什么是MVC?
MVC逻辑示意图
我们接下来通过MVC模式要实现的界面:
文件夹结构:
自定义视图AppView.xib示意图:
控制层:
"ViewController.h"
//
// ViewController.m
//
// Created by Long.
// Copyright © 2016年 LongChuang. All rights reserved.
//
#import "ViewController.h"
#import "AppData.h"
#import "AppView.h"
@interface ViewController ()
@end
@implementation ViewController
{
// 用于接收loadAppData从网络解析得到的数据
// 可以接受可变数组,因为可变数组继承于NSArray
NSArray * _appData;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 1.加载数据
_appData = [self loadAppData];
// 2.刷新UI
[self updateUI];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// 一.第一步加载数据
-(NSArray *)loadAppData
{
// 1.(获取地址)获取url,得到plist文件的位置
NSURL *url = [[NSBundle mainBundle] URLForResource:@"apps.plist" withExtension:nil];
// 2.(通过地址下载)通过数组或者字典的url解析方法加载plist文件,
// *使用nsarray或者NSDictionary的方法,因为集合类型只有这两种
NSArray *arr = [NSArray arrayWithContentsOfURL:url];
// 3.(定义容器用于保存解压得到的数据)解析plist文件中的内容
// 定义一个容器,用于接收解压得到的内容,用可变数组就可以,因为可接收oc对象类型的任何数据
NSMutableArray *arrM = [NSMutableArray array];
// 4.(解压下载得到的压缩包,既未拆封的集合)开始遍历解压,解析下载得到的NSArray中的NSDictionary内容
for (NSDictionary *dic in arr) {
// 5.定义一个解压工具(数据层的功能)
// 解析数据的方法不应该出现在view层,所以应该把解析数据的方法写在model层
// 既写到appData类的方法中
// 首先需要创建一个数据层的类对象,并且把解压得到的数据保存到类的属性中去
// 这里通过调用类方法,在类的方法内部实现了 1.类对象的创建 2.类对象属性的赋值
// 并且类方法的返回值是当前类的实例对象
// 这里定义解压工具的原因是,从网络下载得到的数据,要么是字典,要么是数组,不管是什么集合
// 一般都是提前规定好的,但是里面的key在之后的开发中可能会发生修改或者增加其他key
// 如果plist文件的内容发生改变,我们只需要修改数据的解压工具即可,降低耦合度,增加联动性
AppData *app = [AppData appWithDict:dic];
// 6.把解压得到的数据保存到我们事先准备好的容器中
// 如果我们不通过数据解压工具,那么这里保存的是一个字典
// 现在arrM中保存的是AppData类对象,而且这些类对象中都有和字典key相对应的属性,并且已经赋值
[arrM addObject:app];
}
// 7.返回从网络加载并且解析后得到的所有数据
return arrM;
}
// 二.刷新UI界面
-(void)updateUI
{
// 1.我们可以创建UIButton,UIView
// 这里创建的是我们自定义的视图,名字叫AppView
// 通过调用一个类方法来返回自定义视图,不能使用alloc init是因为需要一些操作来找到我们自定义的视图
// 然后才能初始化,我们把这一系列的操作都封装到了appView的这个类方法中
AppView *appView = [AppView createAppView];
// 2.这里需要一些计算,来根据生成的个数自动的设置我们生成的AppView应该在什么位置
// 定义行数,我们规定一行显示3个应用图标,所以这里设置为3
NSInteger colNum = 3;
// 3.获取自定义视图模板的宽和高
CGFloat appW = appView.bounds.size.width;
CGFloat appH = appView.bounds.size.height;
// 4.计算应用图标与手机屏幕的间距:屏幕的宽度-3个应用的宽度,再除以应用个数+1,因为3个应用会有4个间距
CGFloat margin = (self.view.bounds.size.width - (appW * colNum)) / (colNum + 1);
// 5.根据从网络获取到的数据_appData是一个数组,根据它的长度循环创建自定义视图
for (NSInteger i = 0 ; i < _appData.count; i++) {
// (1) 创建自定义的appView视图
AppView *appView = [AppView createAppView];
// (2) 计算当前的视图属于第几竖列
NSInteger col = i % colNum;
// (3)计算视图x的位置:左间距margin+(自定义视图的宽+间距margin)*当前第几列竖列
CGFloat appX = margin + (appW + margin) * col;
// (4)计算当前的视图属于第几横行
NSInteger row = i / colNum;
// (5)计算视图y的位置:上间距margin+(自定义视图的高+间距margin)*当前第几横行
CGFloat appY = margin + (appH + margin) * row;
// (6)x,y,宽和高,都已经计算好了,直接设置生成的视图位置
appView.frame = CGRectMake(appX, appY, appW, appH);
// (7)给属性appData赋值,使用了set方法,我们在set方法中做了设置
// _appData[i]是一个数组,我们在从网络拉取数据的时候,就使用的AppData类进行的解析
// 所以数组中保存的数据不是字典,是AppData类型的,所以这里可以使用_appData[i]进行赋值
// 赋值的同时通过set方法,修改自定义视图的应用图标和应用名称
appView.appData = _appData[i];
// (8)把生成的自定义视图添加到view主视图上
[self.view addSubview:appView];
}
}
@end
模型层:
"AppData.h"
//
// AppData.m
//
// Created by Long.
// Copyright © 2016年 LongChuang. All rights reserved.
//
#import "AppData.h"
@implementation AppData
+(instancetype)appWithDict:(NSDictionary *) dict
{
// 1.创建appData对象,并且将从字典获取的数据赋值给对象的属性
// 这里使用self创建对象,谁调用谁创建,返回父类
// 根据多态的特性可知,我们这里定义的是父类AppData类型,但实际创建的是调用此类方法的类对象的类型
// 如:一个集成AppData的子类调用,那么创建的就是子类类型
AppData *app = [[self alloc] init];
// 这里用于给类对象的属性赋值,现在假设字典中只有这2个key,我们完全可以手动写入
// 如果字典中有几百个key,写起来就不那么方便了
// app.name = dict[@"name"];
// app.icon = dict[@"icon"];
// 所以,所以,所以,可以调用系统自带的方法
// 可以自动识别app对象中的属性与dict字典中key 并且自动赋值
// 注意:类对象中的属性名称一定要与字典中的key关键字相同
[app setValuesForKeysWithDictionary:dict];
// 这里返回类对象,因为类对象中保存了从字典中解析得到的数据,并且赋值给类对象的属性
// 返回一个属性已经赋值的类对象给view视图
return app;
}
@end
显示层:
"AppView.h"
//
// AppView.m
//
// Created by Long.
// Copyright © 2016年 LongChuang. All rights reserved.
//
#import "AppView.h"
#import "AppData.h"
@interface AppView()
// 自定义视图中的:应用图标
@property (weak, nonatomic) IBOutlet UIImageView *iconName;
// 自定义视图中的:应用名字
@property (weak, nonatomic) IBOutlet UILabel *contextLabel;
@end
@implementation AppView
// 返回当前自定义视图的类对象
+(instancetype)createAppView
{
// 这里相当于我们自定义了一个视图,也是UIView类型的,但是我们不想用系统的UIView类型
// 需要一个自定义的模板
// 第一步就是找到我们自定义的模板
// 第二步就是根据模板创建对象
// 1.(找到自定义模板)自定义的视图是xib格式,与代码中UINib是同一个东西,也叫nib
// 首先需要找到我们自定义的视图nib 这里的@"AppView"不是当前类的名字,是自定义xib的名字AppView.xib
// bundel设置为nil,就表示使用的是mainBundle
UINib *nib = [UINib nibWithNibName:@"AppView" bundle:nil];
// 2.(通过自定义模板创建对象)通过nib实例化对象
AppView *appView = [[nib instantiateWithOwner:nil options:nil]firstObject];
// 3.返回自定义视图对象给方法调用者
return appView;
}
// 在控制层循环创建视图的时候,把从网络获取的数据复制给这个AppData
-(void)setAppData:(AppData *)appData
{
// 把网络数据中的应用图片名字和应用程序名字赋值给视图的属性
_appData = appData;
self.iconName.image = [UIImage imageNamed:appData.icon];
self.contextLabel.text = appData.name;
}
@end