您的位置:首页 > 移动开发 > IOS开发

IOS开发笔记27-应用管理之MVC模式

2015-10-29 21:13 513 查看

1、 九宫格坐标计算

实现以九宫格的形式展示应用信息,点击按钮后能监听按钮单击事件。类似这种类型app往往都是动态加载应用数据,所以我们不可能将数据写死,因为我们不确定应用数量,所以就无法确认控件的数量。最终效果图如下:



界面分析:

一个控件需要显示在界面上,必须为其设置frame和一些必要属性。如果将每一个控件单独的添加到view中,每一个控件的坐标都不同而且计算非常繁琐,也不方便后期坐标的修改。所以我们考虑为每一个应用创建一个单独的view,然后为这个view添加三个子控件,因为每个view中的三个子控件中的坐标都是基于这个单独view而且都是固定的,这样无论是坐标计算还是后期修改子控件坐标都非常方便。

创建项目,拖入使用到的图片素材和plist文件。



app.plist中是一个数组,有12个元素,每个元素有拥有两个字典键值对,分别表示应用的名称、应用图标名。



声明一个数组属性,用于存储plist文件中加载的应用数据并实现加载过程,这里我使用懒加载方式,在需要加载的时候才加载数据,并且只会加载一次。

ViewController.m文件
#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) NSArray *apps;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

//重写_apps属性的get方法,实现懒加载plist文件中的数据
- (NSArray *)apps {
//当属性为nil的时候才加载,这样保证数据只会加载一次
if (_apps == nil) {
//加载app.plist中的应用数据赋值给数组属性
_apps = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"app.plist" ofType:nil]];
}
return _apps;
}
@end
根据九宫格公式,计算每个应用的坐标并显示到屏幕上,计算x坐标和y坐标的原理相同。如下图:



x = 1个横向边距 + (1个宽 + 1个横向边距) * (当前下标 % 每行个数);

y =  1个纵向边距 + (1个高 + 1个纵向边距) * (当前下标 / 每行个数);


假如每个应用宽为viewW,高为viewH,横向边距为appMarginRow,纵向边距为appMarginCol,每行3个应用,则得到下面结果:

第一个应用:
x = appMarginRow + (viewW + appMarginRow) * (0 % 3) = appMarginRow;

y = appMarginCol + (viewH + appMarginCol) * (0 / 3) = appMarginCol;
第二个应用:

2
3
x = appMarginRow + (viewW + appMarginRow) * (1 % 3) = appMarginRow + (viewW + appMarginRow) * 1;

y = appMarginCol + (viewH + appMarginCol) * (1 / 3) = appMarginCol;


第三个应用:

x = appMarginRow + (viewW + appMarginRow) * (2 % 3) = appMarginRow + (viewW + appMarginRow) * 2;

y = appMarginCol + (viewH + appMarginCol) * (2 / 3) = appMarginCol;
第四个应用:

x = appMarginRow + (viewW + appMarginRow) * (3 % 3) = appMarginRow;

y = appMarginCol + (viewH + appMarginCol) * (3 / 3) = appMarginCol + (viewH + appMarginCol) * 1;
第五个应用:

x = appMarginRow + (viewW + appMarginRow) * (4 % 3) = appMarginRow + (viewW + appMarginRow) * 1;

y = appMarginCol + (viewH + appMarginCol) * (4 / 3) = appMarginCol + (viewH + appMarginCol) * 1;
第六个应用:

x = appMarginRow + (viewW + appMarginRow) * (5 % 3) = appMarginRow + (viewW + appMarginRow) * 2;

y = appMarginCol + (viewH + appMarginCol) * (5 / 3) = appMarginCol + (viewH + appMarginCol) * 1;


......

由此发现了一个规律,我们可以给每一行计算出一个行索引,每一列计算出一个列索引。
rowIndex = 当前下标 % 每行个数;

colIndex = 当前下标 / 每行个数;

x = 1个横向边距 + (1个宽 + 1个横向边距) * rowIndex;

y =  1个纵向边距 + (1个高 + 1个纵向边距) * colIndex;


ViewController.m文件

#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) NSArray *apps;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//应用显示的总列数(每行多少个)
int appCol = 3;

//每个应用所占的宽、高固定
CGFloat viewW = 90;
CGFloat viewH = 90;

//一横排(一行)每个应用之间的间距 = ( 大view宽度(屏幕宽度) - 应用总列数 * 每个应用的的宽度 ) / (应用总列数 + 1)
CGFloat appMarginRow = (self.view.frame.size.width - appCol * viewW) / (3 + 1);
//每一排应用直接的间距给一个固定值25
CGFloat appMarginCol = 25;

//self.apps.count表达式中调用了_apps的get方法,应用数据自动加载到_apps数组中,数组中每个元素是一个字典
for (int i = 0; i < self.apps.count; i++) {

//创建应用view
UIView *view = [[UIView alloc] init];

//计算行索引、列索引
int rowIndex = i % appCol;
int colIndex = i / appCol;

//计算应用view的x、y坐标
CGFloat viewX = appMarginRow + (appMarginRow + viewW) * rowIndex;
CGFloat viewY = appMarginCol + (appMarginCol + viewH) * colIndex;

//设置应用view的frame属性
view.frame = CGRectMake(viewX, viewY, viewW, viewH);

//设置应用view的背景色
view.backgroundColor = [UIColor redColor];

//将应用view添加到父容器
[self.view addSubview:view];
}

}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

//重写_apps属性的get方法,实现懒加载plist文件中的数据
- (NSArray *)apps {
//当属性为nil的时候才加载,这样保证数据只会加载一次
if (_apps == nil) {
//加载app.plist中的应用数据赋值给数组属性
_apps = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"app.plist" ofType:nil]];
}
return _apps;
}
@end


2、 应用添加子控件

现在应用view已经创建好了,接下来要创建每个应用view中的子控件,包括应用图标、应用名称和下载按钮。应用view的子控件坐标是以它为基准的,并且每个应用的子控件都是不变的。



ViewController.m文件

#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) NSArray *apps;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//应用显示的总列数(每行多少个)
int appCol = 3;

//每个应用所占的宽、高固定
CGFloat viewW = 90;
CGFloat viewH = 90;

//一横排(一行)每个应用之间的间距 = ( 大view宽度(屏幕宽度) - 应用总列数 * 每个应用的的宽度 ) / (应用总列数 + 1)
CGFloat appMarginRow = (self.view.frame.size.width - appCol * viewW) / (3 + 1);
//每一排应用直接的间距给一个固定值25
CGFloat appMarginCol = 25;

//self.apps.count表达式中调用了_apps的get方法,应用数据自动加载到_apps数组中,数组中每个元素是一个字典
for (int i = 0; i < self.apps.count; i++) {

//创建应用view
UIView *view = [[UIView alloc] init];

//计算行索引、列索引
int rowIndex = i % appCol;
int colIndex = i / appCol;

//计算应用view的x、y坐标
CGFloat viewX = appMarginRow + (appMarginRow + viewW) * rowIndex;
CGFloat viewY = appMarginCol + (appMarginCol + viewH) * colIndex;

//设置应用view的frame属性
view.frame = CGRectMake(viewX, viewY, viewW, viewH);

//设置应用view的背景色
view.backgroundColor = [UIColor redColor];

//将应用view添加到父容器
[self.view addSubview:view];

//-----------创建应用图标控件-----------
UIImageView *iconView = [[UIImageView alloc] init];

//计算应用图标控件的宽高、坐标,并设置frame属性
CGFloat iconViewW = 50;
CGFloat iconViewH = 50;
CGFloat iconViewX = (viewW - iconViewW) / 2;//让图标居中
CGFloat iconViewY = 0;//顶部靠着应用view的,所以y坐标为0
iconView.frame = CGRectMake(iconViewX, iconViewY, iconViewW, iconViewH);

//图标的颜色,到时候替换成数据
iconView.backgroundColor = [UIColor yellowColor];

//添加应用图标控件到应用view,注意不是添加到self.view
[view addSubview:iconView];

//-----------创建应用名称控件-----------
UILabel *nameView = [[UILabel alloc] init];

//计算应用名称控件的宽高、坐标,并设置frame属性
CGFloat nameViewW = viewW;//应用名称宽度和应用view一样宽
CGFloat nameViewH = (viewH - iconViewH) / 2;//让应用名称宽度和下载按钮宽度相同
CGFloat nameViewX = 0;//左边靠着应用view,所以x坐标为0
CGFloat nameViewY = iconViewH;//y坐标就是图标的高度
nameView.frame = CGRectMake(nameViewX, nameViewY, nameViewW, nameViewH);

//应用名称控件背景色,先填充让效果显示出来
nameView.backgroundColor = [UIColor blackColor];

//添加应用名称控件到应用view,注意不是添加到self.view
[view addSubview:nameView];

//-----------创建应用下载按钮控件-----------
UIButton *downView = [UIButton buttonWithType:UIButtonTypeCustom];

//计算下载按钮控件的宽高、坐标,并设置frame属性
CGFloat downViewW = iconViewW;//和应用图标控件一样宽
CGFloat downViewH = nameViewH;//和应用名称控件一样高
CGFloat downViewX = iconViewX;//居中显示
CGFloat downViewY = iconViewH + nameViewH;//应用图标控件和应用名称控件的高度和就是按钮的y坐标
downView.frame = CGRectMake(downViewX, downViewY, downViewW, downViewH);

//下载按钮控件背景色,先填充让效果显示出来
downView.backgroundColor = [UIColor grayColor];

//添加应用下载控件到应用view,注意不是添加到self.view
[view addSubview:downView];
}

}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

//重写_apps属性的get方法,实现懒加载plist文件中的数据
- (NSArray *)apps {
//当属性为nil的时候才加载,这样保证数据只会加载一次
if (_apps == nil) {
//加载app.plist中的应用数据赋值给数组属性
_apps = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"app.plist" ofType:nil]];
}
return _apps;
}
@end


3、 应用子控件添加数据

现在应用管理界面大体框架已经搭建好了,而数据在_apps数组中,每个元素都是一个字典(NSDictionary)。我们可以通过依次取出数组中的每个元素,也就是取出数组中的每个字典,然后通过字典取出字典中的应用图标、应用名称为子控件赋值。



ViewController.m文件

#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) NSArray *apps;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//应用显示的总列数(每行多少个)
int appCol = 3;

//每个应用所占的宽、高固定
CGFloat viewW = 90;
CGFloat viewH = 90;

//一横排(一行)每个应用之间的间距 = ( 大view宽度(屏幕宽度) - 应用总列数 * 每个应用的的宽度 ) / (应用总列数 + 1)
CGFloat appMarginRow = (self.view.frame.size.width - appCol * viewW) / (3 + 1);
//每一排应用直接的间距给一个固定值25
CGFloat appMarginCol = 25;

//self.apps.count表达式中调用了_apps的get方法,应用数据自动加载到_apps数组中,数组中每个元素是一个字典
for (int i = 0; i < self.apps.count; i++) {

//创建应用view
UIView *view = [[UIView alloc] init];

//计算行索引、列索引
int rowIndex = i % appCol;
int colIndex = i / appCol;

//计算应用view的x、y坐标
CGFloat viewX = appMarginRow + (appMarginRow + viewW) * rowIndex;
CGFloat viewY = appMarginCol + (appMarginCol + viewH) * colIndex;

//设置应用view的frame属性
view.frame = CGRectMake(viewX, viewY, viewW, viewH);

//将应用view添加到父容器
[self.view addSubview:view];

//依次取出数组中的每个字典元素
NSDictionary *dict = self.apps[i];

//-----------创建应用图标控件-----------
UIImageView *iconView = [[UIImageView alloc] init];

//计算应用图标控件的宽高、坐标,并设置frame属性
CGFloat iconViewW = 50;
CGFloat iconViewH = 50;
CGFloat iconViewX = (viewW - iconViewW) / 2;//让图标居中
CGFloat iconViewY = 0;//顶部靠着应用view的,所以y坐标为0
iconView.frame = CGRectMake(iconViewX, iconViewY, iconViewW, iconViewH);

//添加应用图标数据
iconView.image = [UIImage imageNamed:dict[@"icon"]];

//添加应用图标控件到应用view,注意不是添加到self.view
[view addSubview:iconView];

//-----------创建应用名称控件-----------
UILabel *nameView = [[UILabel alloc] init];

//计算应用名称控件的宽高、坐标,并设置frame属性
CGFloat nameViewW = viewW;//应用名称宽度和应用view一样宽
CGFloat nameViewH = (viewH - iconViewH) / 2;//让应用名称宽度和下载按钮宽度相同
CGFloat nameViewX = 0;//左边靠着应用view,所以x坐标为0
CGFloat nameViewY = iconViewH;//y坐标就是图标的高度
nameView.frame = CGRectMake(nameViewX, nameViewY, nameViewW, nameViewH);

//添加应用名称数据
nameView.text = dict[@"name"];

//设置文字字体大小、居中显示
nameView.font = [UIFont systemFontOfSize:12];
nameView.textAlignment = NSTextAlignmentCenter;

//添加应用名称控件到应用view,注意不是添加到self.view
[view addSubview:nameView];

//-----------创建应用下载按钮控件-----------
UIButton *downView = [UIButton buttonWithType:UIButtonTypeCustom];

//计算下载按钮控件的宽高、坐标,并设置frame属性
CGFloat downViewW = iconViewW;//和应用图标控件一样宽
CGFloat downViewH = nameViewH;//和应用名称控件一样高
CGFloat downViewX = iconViewX;//居中显示
CGFloat downViewY = iconViewH + nameViewH;//应用图标控件和应用名称控件的高度和就是按钮的y坐标
downView.frame = CGRectMake(downViewX, downViewY, downViewW, downViewH);

//设置按钮默认、高亮状态下的文字、背景图片
[downView setTitle:@"下载" forState:UIControlStateNormal];
[downView setTitle:@"已下载" forState:UIControlStateHighlighted];
[downView setBackgroundImage:[UIImage imageNamed:@"buttongreen"] forState:UIControlStateNormal];
[downView setBackgroundImage:[UIImage imageNamed:@"buttongreen_highlighted"] forState:UIControlStateHighlighted];

//设置按钮文字大小
downView.titleLabel.font = [UIFont systemFontOfSize:14];

//添加应用下载控件到应用view,注意不是添加到self.view
[view addSubview:downView];
}

}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

//重写_apps属性的get方法,实现懒加载plist文件中的数据
- (NSArray *)apps {
//当属性为nil的时候才加载,这样保证数据只会加载一次
if (_apps == nil) {
//加载app.plist中的应用数据赋值给数组属性
_apps = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"app.plist" ofType:nil]];
}
return _apps;
}
@end


4、 字典转模型

以上面这种方式添加数据,如果在调用字典时key值写错Xcode将不会报任何错误,这样有时会造成非常坑爹的BUG。所以如果我们将字典中的数据以对象的方式来进行调用,就能避免类似问题还可以更好的封装数据,也就是MVC中的封装数据模型(Model)。

创建一个类JFApp继承自NSObject,用于封装应用信息数据。类的属性类型根据封装数据的类型来确定,这里app.plist中每个元素是拥有两个键值对的字典,所以定义两个NSString类型的属性分别封装应用图标、应用名称。

JFApp.h文件
#import <Foundation/Foundation.h>

@interface JFApp : NSObject
//应用图标、应用名称属性
@property (copy, nonatomic) NSString *icon;
@property (copy, nonatomic) NSString *name;

//快速创建模型对象的对象方法和类方法
- (instancetype)initWithDictionary:(NSDictionary *)dict;
+ (instancetype)appWithDictionary:(NSDictionary *)dict;

//返回模型数组
+ (NSArray *)apps;
@end


JFApp.m文件

#import "JFApp.h"

@implementation JFApp

//快速创建模型对象的对象方法和类方法
- (instancetype)initWithDictionary:(NSDictionary *)dict {
if (self = [super init]) {
self.icon = dict[@"icon"];
self.name = dict[@"name"];
}
return self;
}
+ (instancetype)appWithDictionary:(NSDictionary *)dict {
return [[self alloc] initWithDictionary:dict];
}

//返回模型数组
+ (NSArray *)apps {
//加载app.plist中的数据到数组
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"app.plist" ofType:nil]];

//定义一个可变数组用于存储模型
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in array) {
JFApp *app = [JFApp appWithDictionary:dict];
//将创建好的模型一次添加到可变数组
[arrayM addObject:app];
}
//返回模型数组
return arrayM;
}
@end


懒加载返回的数组中存储的是模型对象,所以我们需要修改ViewController.m文件中懒加载代码和所有用到字典获取数据的代码。

ViewController.m文件 (改变后的)
#import "ViewController.h"
#import "JFApp.h"

@interface ViewController ()
@property (strong, nonatomic) NSArray *apps;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//应用显示的总列数(每行多少个)
int appCol = 3;

//每个应用所占的宽、高固定
CGFloat viewW = 90;
CGFloat viewH = 90;

//一横排(一行)每个应用之间的间距 = ( 大view宽度(屏幕宽度) - 应用总列数 * 每个应用的的宽度 ) / (应用总列数 + 1)
CGFloat appMarginRow = (self.view.frame.size.width - appCol * viewW) / (3 + 1);
//每一排应用直接的间距给一个固定值25
CGFloat appMarginCol = 25;

for (int i = 0; i < self.apps.count; i++) {

//创建应用view
UIView *view = [[UIView alloc] init];

//计算行索引、列索引
int rowIndex = i % appCol;
int colIndex = i / appCol;

//计算应用view的x、y坐标
CGFloat viewX = appMarginRow + (appMarginRow + viewW) * rowIndex;
CGFloat viewY = appMarginCol + (appMarginCol + viewH) * colIndex;

//设置应用view的frame属性
view.frame = CGRectMake(viewX, viewY, viewW, viewH);

//将应用view添加到父容器
[self.view addSubview:view];

//依次取出数组中的每个模型对象
JFApp *app = self.apps[i];

//-----------创建应用图标控件-----------
UIImageView *iconView = [[UIImageView alloc] init];

//计算应用图标控件的宽高、坐标,并设置frame属性
CGFloat iconViewW = 50;
CGFloat iconViewH = 50;
CGFloat iconViewX = (viewW - iconViewW) / 2;//让图标居中
CGFloat iconViewY = 0;//顶部靠着应用view的,所以y坐标为0
iconView.frame = CGRectMake(iconViewX, iconViewY, iconViewW, iconViewH);

//添加应用图标数据
iconView.image = [UIImage imageNamed:app.icon];

//添加应用图标控件到应用view,注意不是添加到self.view
[view addSubview:iconView];

//-----------创建应用名称控件-----------
UILabel *nameView = [[UILabel alloc] init];

//计算应用名称控件的宽高、坐标,并设置frame属性
CGFloat nameViewW = viewW;//应用名称宽度和应用view一样宽
CGFloat nameViewH = (viewH - iconViewH) / 2;//让应用名称宽度和下载按钮宽度相同
CGFloat nameViewX = 0;//左边靠着应用view,所以x坐标为0
CGFloat nameViewY = iconViewH;//y坐标就是图标的高度
nameView.frame = CGRectMake(nameViewX, nameViewY, nameViewW, nameViewH);

//添加应用名称数据
nameView.text = app.name;

//设置文字字体大小、居中显示
nameView.font = [UIFont systemFontOfSize:12];
nameView.textAlignment = NSTextAlignmentCenter;

//添加应用名称控件到应用view,注意不是添加到self.view
[view addSubview:nameView];

//-----------创建应用下载按钮控件-----------
UIButton *downView = [UIButton buttonWithType:UIButtonTypeCustom];

//计算下载按钮控件的宽高、坐标,并设置frame属性
CGFloat downViewW = iconViewW;//和应用图标控件一样宽
CGFloat downViewH = nameViewH;//和应用名称控件一样高
CGFloat downViewX = iconViewX;//居中显示
CGFloat downViewY = iconViewH + nameViewH;//应用图标控件和应用名称控件的高度和就是按钮的y坐标
downView.frame = CGRectMake(downViewX, downViewY, downViewW, downViewH);

//设置按钮默认、高亮状态下的文字、背景图片
[downView setTitle:@"下载" forState:UIControlStateNormal];
[downView setTitle:@"已下载" forState:UIControlStateHighlighted];
[downView setBackgroundImage:[UIImage imageNamed:@"buttongreen"] forState:UIControlStateNormal];
[downView setBackgroundImage:[UIImage imageNamed:@"buttongreen_highlighted"] forState:UIControlStateHighlighted];

//设置按钮文字大小
downView.titleLabel.font = [UIFont systemFontOfSize:14];

//添加应用下载控件到应用view,注意不是添加到self.view
[view addSubview:downView];
}

}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

//重写_apps属性的get方法,实现懒加载
- (NSArray *)apps {
//当属性为nil的时候才加载,这样保证数据只会加载一次
if (_apps == nil) {
//调用类方法返回一个模型数组
_apps = [JFApp apps];
}
return _apps;
}
@end


5、 xib初体验

应用管理界面搭建、调用数据都已经完成,但是创建应用的子控件的操作非常的繁琐,我们可以利用苹果提供的一款工具xib来简化我们的操作。

创建一个xib文件,因为我们是要描述一个应用view,所以命名为JFAppView,其中JF是前缀。



拖拽一个UIView控件,修改Size选项为Freeform,Status
Bar为None。



和操作我们熟悉的Main.storyboard一样,设置View的宽、高都为90,然后再拖拽UIImage、UILabel、UIButton,并设置其尺寸、字体大小、按钮背景图等等。设置的尺寸和我们上面代码写的子控件尺寸一样。并为每个子控件设置对应的tag值,我这里设置的是UIImage为1、Label为2、Button为3。



加载xib文件并调用我们创建好的View控件

//在app安装后的根目录找出xib文件JFAppView,注意这里不能加扩展名,加载后返回一个数组
NSArray *viewArray = [[NSBundle mainBundle] loadNibNamed:@"JFAppView" owner:self options:nil] ;

//取出数组最后一个元素,因为数组第一个元素是系统自己的view,取出最后一个就能获取到我们自己创建的view
UIView *view = [viewArray lastObject];
根据子控件的tag值调用view的对象方法来创建子控件

//创建应用图标控件,方法返回值是UIView类型,所以需要强转
UIImageView *iconView = (UIImageView *)[view viewWithTag:1];

//创建应用名称控件,方法返回值是UIView类型,所以需要强转
UILabel *nameView = (UILabel *)[view viewWithTag:2];

//创建应用下载按钮控件,方法返回值是UIView类型,所以需要强转
UIButton *downView = (UIButton *)[view viewWithTag:3];
ViewController.m文件(已加载xib中的控件,简写代码)

#import "ViewController.h"
#import "JFApp.h"

@interface ViewController ()
@property (strong, nonatomic) NSArray *apps;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//应用显示的总列数(每行多少个)
int appCol = 3;

//每个应用所占的宽、高固定
CGFloat viewW = 90;
CGFloat viewH = 90;

//一横排(一行)每个应用之间的间距 = ( 大view宽度(屏幕宽度) - 应用总列数 * 每个应用的的宽度 ) / (应用总列数 + 1)
CGFloat appMarginRow = (self.view.frame.size.width - appCol * viewW) / (3 + 1);
//每一排应用直接的间距给一个固定值25
CGFloat appMarginCol = 25;

for (int i = 0; i < self.apps.count; i++) {

//创建应用view
//在app安装后的根目录找出xib文件JFAppView,注意这里不能加扩展名,加载后返回一个数组
NSArray *viewArray = [[NSBundle mainBundle] loadNibNamed:@"JFAppView" owner:self options:nil] ;
//取出数组最后一个元素,因为数组第一个元素是系统自己的view,取出最后一个就能获取到我们自己创建的view
UIView *view = [viewArray lastObject];

//计算行索引、列索引
int rowIndex = i % appCol;
int colIndex = i / appCol;

//计算应用view的x、y坐标
CGFloat viewX = appMarginRow + (appMarginRow + viewW) * rowIndex;
CGFloat viewY = appMarginCol + (appMarginCol + viewH) * colIndex;

//设置应用view的frame属性
view.frame = CGRectMake(viewX, viewY, viewW, viewH);

//将应用view添加到父容器
[self.view addSubview:view];

//依次取出数组中的每个模型对象
JFApp *app = self.apps[i];

//-----------创建应用图标控件-----------
UIImageView *iconView = (UIImageView *)[view viewWithTag:1];

//添加应用图标数据
iconView.image = [UIImage imageNamed:app.icon];

//添加应用图标控件到应用view,注意不是添加到self.view
[view addSubview:iconView];

//-----------创建应用名称控件-----------
UILabel *nameView = (UILabel *)[view viewWithTag:2];

//添加应用名称数据
nameView.text = app.name;

//添加应用名称控件到应用view,注意不是添加到self.view
[view addSubview:nameView];

//-----------创建应用下载按钮控件-----------
UIButton *downView = (UIButton *)[view viewWithTag:3];

//添加应用下载控件到应用view,注意不是添加到self.view
[view addSubview:downView];
}

}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

//重写_apps属性的get方法,实现懒加载
- (NSArray *)apps {
//当属性为nil的时候才加载,这样保证数据只会加载一次
if (_apps == nil) {
//调用类方法返回一个模型数组
_apps = [JFApp apps];
}
return _apps;
}
@end
这样做只是简化了大量创建子控件、设置子控件属性的代码,但还是没有达到我们最终优化的目的。先来了解下MVC设计模式概念,再继续优化我们的代码。

6、 初识MVC设计模式

MVC全名是Model
View Controller,是模型(model)、视图(view)、控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。



视图:数据的展现

视图是用户看到并与之交互的界面。视图向用户显示相关的数据,并能接收用户的输入数据,但是它并不进行任何实际的业务处理。视图可以向模型查询业务状态,但不能改变模型。视图还能接受模型发出的数据更新事件,从而对用户界面进行同步更新。

模型:应用对象

模型是应用程序的主体部分。模型代表了业务数据和业务逻辑。当数据发生改变时,它要负责通知视图部分。一个模型能为多个视图提供数据。由于同一个模型可以被多个视图重用,所以提高了应用的可重用性。

控制器:逻辑处理、控制实体数据在视图上展示、调用模型处理业务请求

当用户单击了应用管理中的下载按钮触发一个单击事件时,控制器接收请求并调用相应的模型去处理请求,然后调用相应的视图来显示模型返回的数据。

7、 根据MVC模式封装我们的应用

根据MVC模式设计思想,我们继续优化我们的应用管理app。让模型、视图与控制器分开,独立完成各自的工作。

先来为我们的xib文件新建一个视图类JFAppView继承自UIView,用于操作xib中的控件和显示控件的数据。注意一定要为xib指定对应的视图类才能进行控件拖线操作。



控件连线操作,需要被访问的控件就设置为属性,能触发事件的控件就设置为方法。



为视图类提供一个快速创建view的类方法,在方法中加载xib文件并为调用者(控制器)返回一个view。而不是让调用者(控制器)自己加载xib创建view。

//快速创建一个view
+ (JFAppView *)appview {
return [[[NSBundle mainBundle] loadNibNamed:@"JFAppView" owner:self options:nil] lastObject];
}
再为视图类提供一个为子控件赋值数据的方法,接收数据模型,并将模型中的数据赋值给对应的子控件。这里也可以将模型设置为视图类的属性,重写这个属性的set方法,在set方法中为模型属性赋值的同时也为子控件赋值。

//接收模型对象为子控件加载数据
- (void)setApp:(JFApp *)app {
self.iconView.image = [UIImage imageNamed:app.icon];
self.nameView.text = app.name;
}
JFAppView.h文件

#import <UIKit/UIKit.h>
#import "JFApp.h"

@interface JFAppView : UIView

//快速创建一个view
+ (JFAppView *)appview;

//接收模型对象为子控件加载数据
- (void)setApp:(JFApp *)app;
@end
JFAppView.m文件

#import "JFAppView.h"

//给视图类添加一个延展,封装属性、方法
@interface JFAppView ()
@property (weak, nonatomic) IBOutlet UIImageView *iconView;
@property (weak, nonatomic) IBOutlet UILabel *nameView;

//下载按钮触发的事件
- (IBAction)downAction;
@end

@implementation JFAppView

//下载按钮触发的事件
- (IBAction)downAction {
}

//快速创建一个view + (JFAppView *)appview { return [[[NSBundle mainBundle] loadNibNamed:@"JFAppView" owner:self options:nil] lastObject]; }

//接收模型对象为子控件加载数据 - (void)setApp:(JFApp *)app { self.iconView.image = [UIImage imageNamed:app.icon]; self.nameView.text = app.name; }

@end
ViewController.m文件(最终版本)

#import "ViewController.h"
#import "JFApp.h"
#import "JFAppView.h"

@interface ViewController ()
@property (strong, nonatomic) NSArray *apps;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//应用显示的总列数(每行多少个)
int appCol = 3;

for (int i = 0; i < self.apps.count; i++) {

//创建应用view
JFAppView *view = [JFAppView appview];
//依次取出数组中的每个模型对象
JFApp *app = self.apps[i];

//传递给视图类中的方法为子控件加载数据
[view setApp:app];

//计算行索引、列索引
int rowIndex = i % appCol;
int colIndex = i / appCol;

//取出view的宽高
CGFloat viewW = view.frame.size.width;
CGFloat viewH = view.frame.size.height;

//计算view的横向边距、纵向边距
CGFloat appMarginRow = (self.view.frame.size.width - appCol * viewW) / (3 + 1);
CGFloat appMarginCol = 25;

//计算view的坐标
CGFloat viewX = appMarginRow + (appMarginRow + viewW) * rowIndex;
CGFloat viewY = appMarginCol + (appMarginCol + viewH) * colIndex;

//设置应用view的frame属性
view.frame = CGRectMake(viewX, viewY, viewW, viewH);

//将应用view添加到父容器
[self.view addSubview:view];

}

}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

//重写_apps属性的get方法,实现懒加载
- (NSArray *)apps {
//当属性为nil的时候才加载,这样保证数据只会加载一次
if (_apps == nil) {
//调用类方法返回一个模型数组
_apps = [JFApp apps];
}
return _apps;
}
@end
控制台中只是创建了一个应用view并设置其frame,接收模型兑现传递给view,并显示应用到屏幕上。这样就完成了简单MVC模式的应用,我们再调整一下Xcode导航结构。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: