【封装xib Objective-C语言】

一、我们先把九宫格应用管理的内容再给大家过一下:

1.上一篇文章,咱们实现了一个应用管理这个案例,

我们实现了哪些重要的功能呢:

1)第一个,我们是懒加载这个数组:

//重写apps属性的getter方法,进行懒加载数据

- (NSArray *)apps{

if(_apps == nil){

//1.加载数据,获取app.plist文件在手机上的路径

NSString *path = [[NSBundle mainBundle] pathForResource:@“app.plist” ofType:nil];

//2.根据路径加载数据

NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];

//3.创建一个可变数组,用来保存一个一个的模型对象

NSMutableArray *arrayModels = [NSMutableArray array];//一个空的可变数组

//4.循环字典数组,把每个字典对象转换成一个模型对象

for(NSDictionary *dict in arrayModels){

//1.创建一个模型

TestApp *model = [TestApp appWithDict:dict];

//2.把模型加到arrayModels中

[arrayModels addObject:model];

}

_apps = arrayModels;

}

return _apps;

}

第一个,我们是懒加载这个数据,懒加载数据的时候,因为数据本身是一个字典的这么一个集合,

我们首先把这个字典,存到一个数组里面以后,接下来,我们把字典转成模型,

2)字典转模型的基本思路就是:

1 > 首先,把数据读到一个字典集合,

2> 第二步,循环字典集合里面的每一个字典,把每一个字典,创建一个对应的模型类,然后把字典存进去,把字典中的每一个键对应的值,赋值给这个模型对应的属性,

3> 第三步,把这个模型加到一个模型数组里面,

4> 第四步,接下来,把模型数组都填满以后,赋值给这个_apps集合,返回这个_apps属性,

然后,当我们这个懒加载值完毕以后,然后这个apps集合当中,就保存了一个一个的模型,一个模型,就对应的是一个字典对象的数据,

然后,为什么要叫它懒加载呢,因为当它只有第一次执行的时候,它的这个_apps == nil ,它是一个nil,等于空的话,才会去加载这些数据,当你第二次再调这个getter方法的时候,它这个_apps就不为空了,就不加载数据了,直接把集合是不是给你返回了,

这是我们这个懒加载数据,

2)有我们这个懒加载数据以后呢,紧接着,我们就在这个viewDidLoad,控制器的这个View,加载完毕以后,接下来,我们在里面通过循环的方式,以这个集合apps当中有多少个元素对象,它就创建多少个view,每个view创建好以后,设置view的一些坐标、frame,

- (void)viewDidLoad{

[super viewDidLoad];

//假设每行的应用的个数=3

int columns = 3;

//获取控制器所管理的view的宽度

CGFloat viewWidth = self.view.frame.size.width;

//设置每个应用的宽和高

CGFloat appW = 75;

CGFloat appH = 90;

CGFloat marginTop = 30;//第一行距离顶部的距离,

CGFloat marginX = (viewWidth - columns * appW) / (columns + 1);

CGFloat marginY = marginX;//假设每行之间的间距与marginX相等

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

//获取当前这个应用的数据

TestApp *appModel = self.apps[i];

//1.创建每个应用(UIView)

UIView *appView = [[UIView alloc] init];

//2.设置appView的属性

//2.1 设置appView的frame属性

//计算每个单元格所在的列的索引

int colIdx = i % columns;

//计算每个单元格所在的行的索引

int rowIdx = i / columns;

CGFloat appX = marginX + colIdx * (marginX + appW);

CGFloat appY = marginTop + rowIdx * (marginY+ appH);

appView.frame = CGRectMake(appX,appY,appW,appH);

//3.将appView加到self.view(控制器所管理的那个view)

[self.view addSubview:appView];

//4.向UIView中增加一些子控件

//4.1 增加一个图片框

UIImageView *imgViewIcon = [[UIImageView alloc] init];

//设置frame

CGFloat iconW = 45;

CGFloat iconH = 45;

CGFloat iconX = (appView.frame.size.width - iconW) * 0.5;

CGFloat iconY = 0;

imgViewIcon.frame = CGRectMake(iconX,iconY,iconW,iconH);

//把图片框添加到appView中

[appView addSubview:imgViewIcon];

//设置图片框的数据

imgViewIcon.image = [UIImage imageNamed:appModel.icon];

//4.2 增加一个Label (标签)

//创建Label

UILabel *lblName = [[UILabel alloc] init];

//设置frame

CGFloat nameW = appView.frame.size.width;

CGFloat nameH = 20;

CGFloat nameX = 0;

CGFloat nameY = CGRectGetMaxY(imgViewIcon.frame);

lblName.frame = CGRectMake(nameX,nameY,nameW,nameH);

//添加到appView中

[appView addSubview:lblName];

//设置Label的数据(标题)

lblName.text = appModel.name;

//设置Label的文字的字体大小

lblName.font = [UIFont systemFontOfSize:12];

//设置文字居中对齐

lblName.textAlignment = NSTextAlignmentCenter;

//4.3 添加按钮

UIButton *btnDownload = [[UIButton alloc] init];

CGFloat btnW = iconW;

CGFloat btnH = 20;

CGFloat btnX = iconX;

CGFloat btnY = CGRectGetMaxY(lblName.frame);

btnDownload.frame = CGRectMake(btnX,btnY,btnW,btnH);

[appView addSubview:btnDownload];

[btnDownload setTitle:@“下载” forState:UIControlStateNormal];

[btnDownload setTitle:@“已安装” forState:UIControlStateDisabled];

[btnDownload setBackgroundImage:[UIImage imageNamed:@buttongreen"] forState:UIControlStateNormal];

[btnDownload setBackgroundImage:[UIImage imageNamed:@“buttongreen_highlighted”] forState:UIControlStateHighlighted];

btnDownload.titleLabel.font = [UIFont systemFontOfSize:14];

[btnDownload addTarget:self action:@selector(btnDownloadClick) forControlEvents:UIControlEventTouchUpInside];

}

}

//按钮单击事件

- (void)btnDownloadClick{

NSLog(@“按钮被点击了…”);

}

@end

子控件增加完毕以后,我们从这个集合这个数组里面TestApp *appModel = self.apps[i];取到当前遍历的这套数据,然后把这套数据里的一些数据里面的内容,赋值给我们子控件的一些属性,这样的话,我们这些控件的数据是不是也就有了吧,

然后呢,当都赋值完毕以后,我们这里给按钮注册了一个单击事件,

然后呢,我们运行完毕以后,就是这样的一个效果:

运行后的效果

然后,后面这两个就是没有用的吧:TestQQApp.h和TestQQApp.m,两个文件,

这两个文件是没有任何意义的,就是为了给大家演示字典转模型时把返回值写为“TestApp *”为什么不合适的,因为子类继承时,返回的还是父类的类型,

把这两个给删了吧,

4)然后,我们还有一个,就是计算九宫格的那个坐标,计算九宫格的那个坐标,你下来把它再看一遍,熟练一下那个思路,就可以了,

关键那么几点,就是:

1 > 第一点,先确定每一个View的高和宽,

2 > 第二点,计算View和View之间的间距,水平间距,还有距上方这个间距,以及垂直间距,

3 > 第三点,把这三个间距拿到以后,第三步,计算任何一个View的它所在的列的索引,和它所在的行的索引,把这两个值拿到以后,X和Y就能计算了,也就是我们计算这个坐标,这么几点,大家需要注意的,

二、好,接下来,我们就给大家说,另外一个问题,

是什么问题呢,就是之前我们在动态创建每一个应用的时候,在这里,是不是写了一堆去动态创建子控件的代码,在这里写了一堆,这些代码都是动态去创建每一个View下面那些子控件的代码吧,

这些代码写在这里,非常啰嗦,非常多,

那么我们每次需要实现九宫格的时候,这些代码是不是我们现在都要写一遍,都要写一遍,非常不方便,

我们解决办法就是:通过一个xib,来描述一个界面,

那么,xib是什么,还记得吧,xib实际就是用来描述一个软件界面的这么一个文件,

那么,就是我们通过鼠标拖、拉、拽,能把控件拖上去,就像我们这里的storyboard一样,可以把控件直接拖上来,就能以可视化开发,所见即所得,这个效果,看到的样子,运行起来,就是这么一个效果,

ok,这是我们这么一个xib的作用,那么,之前给大家介绍了一下这个xib和这个storyboard的区别,还记得吗:

1)xib一般用来描述局部的一个界面吧,

而我们storyboard可以描述多个这个UI界面,可以就是让我们多个整个手机界面,可以放多个控制器,每个控制器之间是如何跳转的,那个连线关系,也可以通过storyboard来描述,

但是一般情况下,我们xib只用来描述局部的界面,

我们这里运行起来以后,希望用xib来描述,是不是我们的一个应用啊,

希望用xib描述这一个应用,然后这一个应用,我反复让它加载十几次,这样的话,是不是看到有十几个这个应用界面了,

ok,那么接下来,我们就试着用xib给大家描述一下这个应用,

2.那么首先,我们要用xib,所以我们要新建一个xib

注意,“叉爱必”和xib是不是一回事儿,是一回事儿,

注意看,最右边那一列,最上面有个黄色的01应用管理文件夹,注意,这是一个文件夹吗,这是一个Group ,这叫一个Group ,这是一个组,

这是逻辑上的这么一个组,事实上在磁盘上,有这个文件夹吗,没有,

例如,我们右键点这个蓝色的01应用管理项目名称,选择New Group,再建立新的一个组,建完之后,你选择蓝色的01应用管理项目名称,右键,选择Show in Folder,磁盘上看得到这个文件夹吗,看得到这个新建的组吗,是不是看不到,

所以说,这只是逻辑上的一个文件夹,这样使我们项目看起来更清晰一些吧,这叫一个Group,

好,那我们在这里给它新建一个xib,右键点击黄色的01应用管理这个组,选择New File,

我们说,xib文件,是不是用来描述软件界面的,软件的界面,是不是就是软件和用户打交道的一个接口,所以你这里选择User Interface,

不是选择Storyboard,不是选择View,不是选择Empty,其实这两个差别不大,这个Empty是完完全全的一个空的xib,建好以后里面啥都没有,完全需要自己拖,

View,这是里面有View,

我们就建一个这个Empty,空的xib,里面什么都没有,什么都没有,都需要你自己拖,

那么,给这个xib起个名字,叫什么,还记得我们这个项目的类前缀叫什么吗,叫Test,所以写上Test,

然后这个xib是做什么的呢,是用这个xib来描述一个应用这个界面吧,

一个应用就是一个App,所以叫TestApp.xib,因为用来描述这个应用吧,一个应用就是一个UIView,所以叫TestAppView.xib,

这样的话,这个xib就是用来描述一个应用的View的这个东西,这是我们起名字的一个思路,

之后,点击Create,创建完了xib之后,大家看,这里有什么东西吗,没有,

注意,我们要的这个应用,一个应用就是一个小的View吧,

所以说,我们要的这个xib,描述一个View,所以说,我们就要先在xib上拖一个View出来,

如果你希望xib描述其他东西,你就拖一个其他东西出来,

因为我现在一个应用就是一个UIView:

//1.创建每个应用(UIView)

UIView *appView = [[UIView alloc] init];

所以说,我希望一个xib描述一个UIView,所以说,我创建好这个xib,我向里面要先拖一个UIView出来,

找到工具箱,是不是这个就是一个UIView:View,

把它拖到xib的界面里,

3.然后,拽进来之后,你是不是发现它好大好大的,

想让它变小点儿,怎么办,现在拖是不管用的,

最右边,上边,有一个小尺子,上面有这个UIView的高和宽:Width = 600,Height = 600,

现在它的高和宽能改吗,改不了,这和自动布局的适配是有关的,我们暂时不适配,所以这里高和宽改不了,

它是600,600,现在我们要想让它改变大小,怎么办,

得选中这个属性,找到这个View的属性,在这个Size里,把它改成Freeform,自由大小,

改完以后,看它上面是不是有小点点了,三个点,

三个点,表示可以改变大小了,

好,回到小尺子的界面,改一下这个View的宽和高,

还记得咱们一个应用是多宽啊,75吧,

一个应用是多高啊,90吧,

好,这样的话,我们是不是一个应用是这么大啊,

你说:那这个电池图标怎么办,

就当没有它吧,运行起来就没有了,

4.现在有了View以后,我们是不是要根据我们这里每个应用的这个设置,来调整这个View啊,

一个View里面,是不是有一个图片框,一个Label,一个按钮吧,

这三个,就不需要我们动态写代码去创建了,就在这里拖就行了,

怎么拖呢,我们从工具箱里,先拖一个图片框进来,UIImageView,

把图片框拖到这个View里面,它会自己变小,

注意:这个图片框的大小,是填满整个这个View吗,不是,

我们要的是:不是填满整个这个View吧,那现在是填满整个这个View吗,现在是填满整个这个View,我们要的不是填满整个这个View,

我们要的这个图片框的高和宽,是45,45吧,

用小尺子把这个图片框的宽和高,都改成45 , 45

既然这个图片框的宽和高,都是45,整个这个UIView的宽是75,那要让它居中,X和Y是多少,它距左边多少,

(75 - 45)* 0.5 = 15,

所以X = 15 ,Y = 0 ,

5.然后,接下来,是这个Label,

把Label拽过来,拽过来以后,我们调整它吧,

整个这个View的宽度,是75,

我们就调整这个Label的宽度,是75,

注意,这时候,最好别在这里拖拉拽,你在这里拖拉拽的话,会对这个View造成影响,

还是用小尺子,把这个Label的宽调整成75,

它距离左边,就是一个0,所以X = 0,

然后,它本身的高度呢,调整成20,

它距离上边的Y值,是不是等于我们这个图片框的高度啊,图片框多高,45,

所以,这个Label的Y值,是45,

这个Label里面的文字,是不是需要变小一些,所以打开这个Label的属性,属性里面有一个Font,Font右边有一个“T”按钮,打开这个按钮,里面有一个Size,Size可以调整Label里面字体的大小,把它改小一些,改成12,

然后,希望Label文字居中,怎么办,

属性里面有一个Alignment,对齐方式,设置中间的那个选项,居中显示文字,

这样的话,这个标签是不是就ok了,

6.然后,下面再拽一个按钮上去,

然后,我们说,Button这个宽,是45吧,

Button这个宽,和图片框的宽,是一样的,

它距离左边,居中,就是X = 15,

它的高度,可以给它调整,调整为20,

按钮的Y坐标,是多少,就是这个图片框的高度,加上这个Label的高度,

图片框多高,45,这个Label多高,20,加起来是多少,65吧,

所以,这个按钮的Y值,就是65,

然后,我们按钮上这个文字,要变成什么文字,是不是“下载”,

按钮中的文字大小,是不是也要调整一下,调整成13吧,

然后,你看起来,是不是这个效果了,

然后,这个按钮,是不是要给它两张背景图,一张是“buttongreen”,另一张是“buttongreen_highlighted”,

一张是默认状态下的背景图,一张是高亮状态下的背景图,

所以,我们选中这个按钮以后,找到它的Background属性,注意,不是image属性,image属性,是调整按钮的图片,按钮显示的图片,Background,才是调整它的背景图,

找到它默认状态下的Background属性,State Config:Default,Background:buttongreen,

找到它高亮状态下的Background属性,StateConfig:Highlighted,Background:buttongreen_highlighted,

这样的话,这个背景图就有了 ,

但是发现这个按钮的文字,是什么颜色,

默认是蓝色,怎么去改这个文字颜色,把它变成白色

当然,改这个Text Color,是可以的,

或者说,你可以把这个类型,Type,改成Custom,自定义,

文字就自动变成白色了,

并且,这样的话,单击时候没有灰色,

对,字体是不是也变了,你再把字体改过来,

或者你不改这个Type:Custom,的话,

你还是Type:System,的话,

你就把这个Text Color : White Color,

注意,你把这个Type:Custom,的话,点击的时候,是没有那个灰色效果的,其实我们也不需要那个灰色效果,因为我们是不是已经给了它一张背景图了,给了它高亮状态下的背景图了,

好,这样的话,我们这个xib,就创建好了,

现在,我们已经通过xib,创建好了这个界面,接下来,我们先尝试把这个xib,也就是这个View,加载显示到我们这个界面上,

加载显示到我们这个界面上以后,我们这个界面,就不再用代码来加载了吧,

二、把xib加载显示到我们这个界面上来,

1.好,创建好这个View以后,打开我们这个控制器,也就是ViewController.m文件,

控制器里面,刚才我们这一堆代码,是不是都是动态创建子控件的一些效果啊,

注意,刚才我们在循环里面,每次创建一个View,是不是直接完完全全,是这么动态来写的啊:

//1.创建每个应用(UIView)

UIView *appView = [[UIView alloc] init];

现在,我们不要这么写了,

我们这里不要通过[[UIView alloc] init],来创建,

我们要通过加载xib的方式来创建,是不是要把这里的代码改一下,

与此同时,注意,我们后面这些动态加载子控件的代码,还要吗,是不是也不要了,因为我们xib里面,这些子控件是不是都已经设置好了,

这些代码,都可以给它删掉了,删掉之前,可以先保存一份这个工程的副本,

好,在这段代码里面,xib已经ok了,接下来我们就开始,修改一下控制器里面的代码

让所有的这些代码,都折叠起来,

Shift + option + command + 左

这个是折叠所有的代码,

让所有的这些代码,都展开:

Shift + option + command + 右,

这个是展开所有的代码,

ok,我们在这个viewDidLoad里面,

我们既然要通过xib来创建这个View,

所以说,后面这些创建View的代码,以及向View里面增加子控件的代码,是不是都可以删掉了,

从//4. 向UIView中增加子控件,

开始,把下面的代码都删掉,

剩下的代码是这些:

- (void)viewDidLoad {

[super viewDidLoad];

// Do any additional setup after loading the view.

//1.假设marginX,marginY,marginTop,columns

//1.1 假设每一行有3列,

int columns = 3;

//1.2 假设每一个应用的宽度是75 , 高度是90

CGFloat appW = 75;

CGFloat appH = 90;

//1.3 计算出marginX,

CGFloat marginX = (self.view.frame.size.width - columns * appW)/(columns + 1) ;

//1.4 假设marginY 与marginX相等

CGFloat marginY = marginX;

//1.5 假设marginTop 是30

CGFloat marginTop = 30 ;

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

//0.设置数据模型

TestApp *appModel = self.apps[I];

//1.创建应用的View,

UIView *appView = [[UIView alloc] init];

//2.设置应用的View的frame

CGFloat appX = marginX + (i % columns) * (marginX + appW);

CGFloat appY = marginTop + (i / columns) * (marginY + appH);

appView.frame = CGRectMake(appX, appY, appW, appH);

//3.把应用的View加到self.view里面

[self.view addSubview:appView];

}

}

1)首先,我们这里就for循环,获取数据,

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

//获取当前这个应用的数据模型:

TestApp *appModel = self.apps[i];

2)第二,创建一个干干净净的View,

UIView *appView = [[UIView alloc] init];

3)第三,设置坐标:

//计算每个应用所在的列索引:

int colIdx = i % columns;

//计算每个应用所在的行索引:

int rowIdx = i / columns;

CGFloat appX = marginX + colIdx * (appW + marginX);

CGFloat appY = marginTop + rowIdx * (appH + marginY);

appView.frame = CGRectMake(appX,appY,appW,appH);

4)第四,然后把应用加进控制器所管理的View里面吧:

[self.view addSubview:appView];

是不是又回到我们一开始的代码了,

但是,现在我们有了xib,

所以,我们这里不要这么做了,这么做,是不是创建了一个全新的View,里面没有组建吧:

UIView *appView = [[UIView alloc] init];

我们需要通过xib来创建View:

5)第五,通过xib来创建View:

//1.通过xib创建每个应用View(UIView)

UIView *appView =

注意:通过xib来创建View,你既然要通过这个xib来创建View:TestAppView.xib,

1)首先,是不是要获取这个xib文件,

2)然后,再加载这个xib文件,拿到xib文件里面那个对应的View,是不是才可以吧,

3)既然你要获取这个xib文件,就要拿到这个xib文件在安装到手机上之后,这个xib文件的路径,

就相当于是加载plist文件和图片一样,

得先找到这个应用安装到手机上之后的根目录,在根目录上搜索这个xib的路径,然后是不是才能把它加载起来,

是不是基本就类似于这个思路吧,

4)那么,接下来,就给大家写代码,看一下如何去加载这个xib文件:

6.加载xib文件:

//1.通过xib创建每个应用View(UIView)

//通过动态加载xib文件创建里面的View:

//你要加载这个xib,是不是首先要拿到这个xib啊,

//1.1> 找到应用的根目录:

NSBundle *mainBundle = [NSBundle mainBundle];

//好,这样就拿到我们应用程序的根目录了吧,这样就拿到了我们应用程序的根目录,注意,我们现在是分开两步来写的啊,

//我们把这个根目录打出来看一下吧,

NSLog(@“%@”,[mainBundle bundlePath]);

//这个mainBundle,调用这个bundle的bundlePath,就是获取当前这个mainBundle所对应的路径,

//咱们先给大家看一下,看一下这个路径是什么路径,看一下这个应用程序的根目录是在哪里,

//好,在这句话这里,设个断点,看一下:

NSBundle *mainBundle = [NSBundle mainBundle];

//这句话,输出这个路径:

NSLog(@“%@”,[mainBundle bundlePath]);

//输出:2023-02-2314:21:13.067 01应用管理[1843:92402] /Users/apple/Library/Developer/CoreSimulator/Devices/

59043416- -6B5A- 4B46 -87EA-0BF9EB860E5E/data/Containers/BundLe/Application/

FBF33379- -39CF- 45C4- -8C16- 1B6DE1D618D1/01应用管理. app

后面是不是来了个01应用管理.app

这个01应用管理.app,就是我们应用安装的根目录,

我们先不找这个根目录,我们先把前面的路径复制出来,给大家看一下,打开Finder,点击“前往”,点击“前往文件夹”,把前面的这些复制粘贴过来:

2023-02-2314:21:13.067 01应用管理[1843:92402] /Users/apple/Library/Developer/CoreSimulator/Devices/

59043416- -6B5A- 4B46 -87EA-0BF9EB860E5E/data/Containers/BundLe/Application/

FBF33379- -39CF- 45C4- -8C16- 1B6DE1D618D1/

看到这个文件夹了吗,因为我们现在是在模拟器上,并不是在真正的手机上,所以这个模拟器的路径,里面你看到这个01应用管理.app,这就是个文件夹,这个文件夹,就是我们这个应用程序的安装到手机上之后,这个应用程序的根目录,

你在这个01应用管理上,点“右键”,选择“显示包内容”,看到的这个包内容,就是我们应用程序的根目录,

这就是我们的应用程序安装到手机上以后的根目录,

那么,我们无论搜索plist文件也好,搜索什么文件也好,都是在这个根目录下搜索啊,

就是这个plist文件,当应用程序安装到手机上以后,它是不是也是安装到我们这个根目录下啊,

那么,拿到这个mainBundle以后,现在大家看到这个mianBundle是个什么东西了吧,

mainBundle,指向的就是我们应用程序安装到手机上以后的那个根目录,

好,接下来,我们要在这个根目录下,搜索我们的xib,

大家说,这个目录下,有我们这个xib文件吗,TestAppView.xib,

没有吧,

我们是不是暂时没有看到吧,我们看到的是不是一个nib文件啊,TestAppView.nib,

这个其实对应的就是我们的xib,这个nib文件,用记事本什么的,啥也打不开,

我们在开发的时候,这个后缀是xib,但是安装到手机上之后,这个后缀就变成nib,了

这就是经过加密以后的这么一个文件,

这就是我们的nib文件,开发的时候叫xib文件,

当程序运行的时候,是不是应用程序已经部署到手机上,再运行了,

所以说,当我们要加载这个xib文件的时候,实际上就是加载我们这个nib文件,

nib就是xib,nib是当应用程序部署到手机上以后,就变成nib,了

开发的时候,叫xib,

所以说,我们加载的时候,其实就是加载这个nib,nib文件,

好,拿到这个mainBundle以后,接下来,就开始加载我们这个xib里面这个View,

1)第一步,获取手机上的根目录,

//1.通过xib创建每个应用View(UIView)

//通过动态加载xib文件创建里面的View

//1.1> 找到应用的根目录,

NSBundle *mainBundle = [NSBundle mainBundle];

2)第二步,在应用程序根目录下,去搜索对应的xib(nib)文件,

怎么搜索呢,是这样搜索的:

//之前这句话是这样的:

UIView *appView = [[UIView alloc] init];

//现在改成这样:

//mainBundle,是刚才我们拿到的这个手机上的根目录吧,

//NSBundle *mainBundle = [NSBundle mainBundle];

//后面这个叫mainBundle,前面这个我也可以不叫mainBundle,可以叫rootBundle,

根目录,是不是都可以啊,

这样的话,自己起个名儿,

NSBundle *rootBundle = [NSBundle mainBundle];

//然后调它,有这么一个方法,叫做NSArray * loadNibNamed:(NSString *) owner:(id) options:(NSDictionary *)

//注意,为什么叫loadNibNamed,因为事实上,它是不是就是nib文件啊,

//这个地方,只需要传一个xib的文件名,这个xib的文件名叫什么,TestAppView,吧,

//好,特别提醒:这里不需要写后缀,

//为什么,它本身加载就是后缀nib的文件,它会自动给你补全的,你如果写上后缀,反而找不到了,

//这里不能写后缀,

//然后,后面owner,暂时都先传nil,

//然后,后面options,暂时都先传nil,

//暂时都先传空,

//好了,这样的话,这样一句话,就可以加载我们的xib,

UIView *appView = [rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil];

//但是你仔细看,这儿给你报了个警告了,

//你仔细看,这个loadNibNamed,这个返回值,是啥类型啊,NSArray 吧,

//是一个数组类型吧,

//好了,接下来,给大家解释一下,这里为什么返回的是一个数组,注意,

//因为,我们在这一个xib中,现在我们是不是只拽了一个View啊,

//我还可以在这个xib中,再拽一些其他任何的子控件,都是可以的,

//想拽什么就拽什么,

//请问,当前这个xib,加载起来以后,里面有几个控件,

//是不是有8个啊,

8个子控件

那么,为什么这个loadNibNamed返回的是一个数组呢,

正是因为这儿有8个子控件,所以说,这个数组里面,就存在的是8个控件对象,

数组里面存在8个控件对象,

也就是说,为什么它返回的是一个数组,它就是因为这个xib里面,可以有多个子控件,

当你拽了多个子控件以后,它返回的就是所有子控件的一个集合,

所以说,是一个数组,

当我把其他所有子控件都删掉,只留下了一个,虽然只留下了一个子控件,但是,它返回的还是一个数组,

因为,人家怎么知道你将来会有几个,所以,它返回的还是一个数组,

那么,你怎么样能拿到这个数组里面唯一的一个对象呢,

firstObject,lastObject,索引为0的,是不是都可以啊,

所以说,既然它是一个数组,我们要拿到这个数组里唯一的一个子控件,

就是使用firstObject,lastObject,都是可以的,

这样,就拿到我们这个xib里面唯一的那一个控件了:

UIView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

因为,lastObject,返回的是一个id,

所以,你前面拿一个UIView接收,是不是也是可以的,

好,再回顾一下我们这个for循环,for循环里面,

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

//获取当前这个应用的数据模型:

TestAppView *appModel = self.apps[i];

//1.通过xib创建每个应用

//通过动态加载xib文件创建里面的View

//1.1> 找到应用的根目录:

NSBundle *rootBundle = [NSBundle mainBundle];

//1.2> 在应用程序根目录下去搜索对应的xib文件:

UIView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

for循环里面,通过xib加载了一个View,这里并没有给这个View添加任何的子控件,

请大家思考一下,这时候,运行起来的时候,这个View里面,会有子控件吗,

有,为什么有,因为我们这里是通过xib创建了一个View,这里不是通过alloc init创建View,

这里是创建了一个xib里面的View,

xib里面的View,是有子控件的,

运行一下,看看,是不是每一个子控件都是有的,

虽然每一个子控件都是有的,但是你能看到它里面具体的数据吗:

通过xib创建的View

是不是我们是看不到子控件里面具体的数据的,

你要想看到这些子控件的具体数据,得咋办,是不是得一个一个给这个xib里面的子控件设置具体的数据啊,

所以,接下来,第二步,是给子控件设置数据吧,

三、给xib里面的子控件设置数据

1.第二步,就要给通过xib加载起来的这个View,给这里的子控件设置数据,

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

//获取当前这个应用的数据模型:

TestAppView *appModel = self.apps[i];

//1.通过xib创建每个应用

//通过动态加载xib文件创建里面的View

//1.1> 找到应用的根目录:

NSBundle *rootBundle = [NSBundle mainBundle];

//1.2> 在应用程序根目录下去搜索对应的xib文件:

UIView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

//2.2 设置appView的frame属性

//计算每个应用所在的列的索引

int colIdx = i % columns;

//计算每个应用所在的行的索引

int rowIdx = i / columns;

CGFloat appX = marginX + colIdx * (appW + marginX);

CGFloat appY = marginTop + rowIdx * (appH + marginY);

appView.frame = CGRectMake(appX,appY,appW,appH);

//3.将appView加到self.view(控制器所管理的那个View里面)

[self.view addSubview:appView];

//4.设置appView中的子控件的数据,

//把appView加到控制器所管理的View里面以后,接下来,就是设置appView中的子控件的数据:

//大家思考一下,我们这些数据,现在在哪里,是不是在模型里面啊,

//是不是在appModel,这个里面啊,

//appModel.name,appModel.icon,是不是根据它的属性,设置数据,

//现在数据在这个appModel里,怎么样把它设置给这个xib中的子控件呢,

//我们现在要访问xib这个界面上这些控件,

//这时候,最好的办法,就是通过拖线,用一个属性来引用它吧,

//用一个连线,通过属性来引用这个xib上面的子控件,是不是就能通过属性访问这个xib里面的子控件了,

//但是你说,现在没有连线,但是你偏偏就是要访问这里面的子控件,那怎么办呢,

//这时候,你可以给这个xib中的每一个子控件,一个Tag,然后通过viewWithTag,是不是也能拿到,

//但是,这样做并不好,我先给你演示一下这个不好的办法,希望大家有所了解,

//1)我可以怎么做呢,我可以为这个xib里面的第一个view,为这个图片框,为它设置一个Tag值,在图片框的属性里,有一个Tag属性,给它一个1000,

//2)为这个Label,起一个Tag,2000,

//3)为这个下载按钮,起一个Tag,3000,

//4)现在,每一个子控件,是不是有一个Tag值了,

//然后,思考一下,我在这里,ViewController.m文件中,怎么拿到每一个子控件啊,

//你要想通过Tag,拿到这个子控件,首先得找到这个子控件的父控件吧,

//调用它的父控件里面的viewWithTag方法,是不是就能拿到这个子控件了,

//那么,这些子控件所在的父控件,就是什么,是不是appView啊,就是appView,

//[appView viewWithTag:1000];

//这样拿到的就是谁,是不是我们第一个子控件图片框啊,好,我用UIImageView接一下:

UIImageView *imgViewIcon = [appView viewWithTag:1000];

//这里报了个警告:Incompatible pointer types initializing ‘UIImageView’ with an expression of type ‘UIView’,

//这个viewWithTag的返回值,是一个UIView类型,不是我们这个UIImageView类型吧,我们给它强转一下:

UIImageView *imgViewIcon = (UIImageView *)[appView viewWithTag:1000];

//拿到第一个子控件以后,是不是要设置第一个子控件里面的数据,

imgViewIcon.image = [UIImage imageNamed:appModel.icon];

//这样,是不是就设置好第一个子控件的数据了,接下来,是设置Label的数据,

//5)设置Label的数据,

UILabel *lblName = (UILabel *)[appView viewWithTag:2000];

lblName.text = appModel.name;

//这样的话,这两个子控件的数据,是不是有了,

//我们运行一下,试一下,是不是可以了,图片和文字都有了吧,可以通过这个viewWithTag,拿到当前我们这个appView下面的子控件吧,

//但是这样做好吗,谁知道你这个1000,2000,3000,是什么,谁能解释一下这样做有什么不好:

//1)如果这个appView里面有很多个子控件,每一个子控件又有不同的代码,你知道这个1000,2000,3000,是什么吗?是不是不太好区分啊,

//这只是一个问题,其实还有一个更重要的问题,

//2)这样写,就是依赖性太强了,造成了“紧耦合”,紧耦合,

//也就是说,你在控制器里的这段代码,紧密的依赖于这个xib中的子控件的Tag,

UIImageView *imgViewIcon = (UIImageView *)[appView viewWithTag:1000];

imgViewIcon.image = [UIImage imageNamed:appModel.icon];

UILabel *lblName = (UILabel *)[appView viewWithTag:2000];

lblName.text = appModel.name;

//这段代码,紧密的依赖于这个xib里面的子控件里面的Tag,

//当xib里面的View的Tag,改变了以后,这段代码,是不是就费了,

//这就叫做“紧耦合”,紧密的依赖于另外一个地方,这段代码不能独立的存在,必须依赖于xib里面的view的Tag,没有那个Tag,这段代码就费了,Tag值改了,这段代码是不是也费了,

//所以说,我们不能过分依赖于另一个地方,这就叫做“紧耦合”,

//比如说,你这个手机,和你这个手机里面的电池,就是紧耦合,你这个手机,和你的耳机,就是松耦合,

//假设你这个手机,是一个诺基亚手机,你又买了个三星手机,你这个诺基亚手机费了之后,假设你这个诺基亚手机里面的电池还是好的,思考一下,你这个诺基亚手机里面的电池,能分给三星手机用吗,不能,为什么,因为型号不一样吧,

//再假设一下,你的手机,是一个诺基亚手机,它有一个3.5毫米接口的耳机,你又买了一个苹果手机,假设你这个诺基亚手机费了,思考一下,你这个耳机,能接着给苹果手机用吗,可以吧,

//为什么,因为你设计电池时,紧密依赖于特定款的诺基亚手机,来设计这个电池,

//你这个电池,是紧密依赖于特定款的诺基亚手机的,一旦你的这个诺基亚手机费了,这个电池,别人谁都不能用它,

//所以说,你设计这个电池的时候,就依赖于特定款的手机,所以说,这个电池,它不能独立存在,它独立存在是没有意义的,

//这就是,这个电池和这个型号的诺基亚手机是“紧耦合”,这个电池和这个手机“紧耦合,”

//但是,这个诺基亚手机的耳机,它在设计时,就没有依赖于特定型号的手机,因为所有的耳机,是不是都是一个接口标准,所以这个耳机,拿到哪儿都可以用,

//因为这个耳机,独立出来以后,它还是一个完整的耳机,是不是还可以被用啊,这个耳机拿到哪儿都可以用,

//所以说,这个耳机和这个手机,就是松耦合的,它俩互相谁都不依赖于谁,谁都可以独立存在,

//回过头来,再看我们的代码:

UIImageView *imgViewIcon = (UIImageView *)[appView viewWithTag:1000];

imgViewIcon.image = [UIImage imageNamed:appModel.icon];

UILabel *lblName = (UILabel *)[appView viewWithTag:2000];

lblName.text = appModel.name;

//如果你在控制器里这么去写代码,证明你在设计代码时,就是特定于、依赖于View里面的Tag,

//一旦这个Tag没有了、Tag费了、Tag变了,你这儿这些代码就得改,或者说,你这儿的代码就费了,

//也就是说,你这样写,还是依赖性太强了,过分依赖于View里面的Tag,

//所以说,你这么写并不合适,

//那么,我们怎么写呢,我们还是通过大家最熟悉的连线的方式:

6)通过连线的方式,拿到子控件,

我们把这个View里面的子控件,通过连线的方式,用一个属性来引用它,是不是我们通过这些属性,就可以访问到这些子控件了,

现在的问题是:我通过加载这个xib返回这个View,我拿了一个什么样的类型的变量来接收:

UIView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

是不是拿了一个UIView类型的变量来接收,

为什么我这里要拿一个UIView类型的变量来接收呢,因为:

你在xib中,拽的这个View,这个View,你看它的属性里面,

它这个View所对应的类,是一个什么类,在xib中,选中左边那一列子控件上面的View,

看右边的Custom Class属性,里面有一个Class:是不是UIView类型,

也就是说,其实它内部,根据这个xib创建它的时候,其实就是一个UIView类型,

所以说,我在控制器里面,通过xib加载一个View,这里是不是要用一个UIView来接收这个变量吧:

UIView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

我要通过拖线的方式,把这个View里面的每一个子控件拖线,是不是要用一个属性来引用啊,

你现在这个View,指向的是一个系统的UIView,这个系统的UIView,是我们框架里已经写好的一个类型吧,

你能在这里面给它增加三个属性,来引用这三个子控件吗,不行,

所以说,一般情况下,你在xib里面,通过xib方式来描述一个控件,一般情况下,都会为这个控件,自定义一个类,

在这个自定义的类里面,写我们的代码,

我们现在,要通过拖线的方式,把这3个子控件,用3个属性来引用,

那么现在这个UIView,是用的系统的UIView类,我们不可能去拖线,拖到系统的UIView类里面,

因为这个类已经写好了,我们不可能去改它的代码吧,

所以说,这里只能是我们自己写一个类,让这个类继承自UIView,然后把那个类填到这个地方:

Custom Class的Class属性里:

然后,接下来,当你这儿填好这个类以后,你下次再通过xib加载这个View,然后它创建的就是,你这儿写的是哪个类,就是那个类的对象,

然后,你在拖线的时候,就往那个类里面拖,

这样的话,就可以拖进去了,

7)好,既然我们知道这里,需要一个自定义类了,所以,我们这里给它建一个自定义类,

那么,这个自定义类,将来继承自什么,UIView,

那么,这个自定义类,类名叫什么呢,

因为,你这个类,是用来描述你这个xib里面的View,所以说,你这个自定义的类的类名,最好和这个xib的文件名是一样的,

这样的话,别人看起来就知道,它们是一起的,

选中最左边,最上面的黄色的01应用管理这个文件夹,右键,New File,Source,Cocoa Touch Class,Next,

Class名字:给个TestAppView,

Subclass of:给个UIView,

继承自谁啊,UIView,

好,这样的话,这个类就写好了,

这个类,是不是继承自UIView的,然后把这个类的类名,command + C,找到xib,找到xib里面的这个View,复制粘贴到这个View最右边的属性:Custom Class下面的Class:里面

好,当把这个类名写在这儿以后,这个时候,我们再通过xib加载这个View的时候,它给你创建的对象,就是这个TestAppView类型的对象,就不是一个UIView了,

所以说,我们控制器里面,这里是不是需要改一下:

UIView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

所以说,我们控制器里面,这个地方接收的时候,应该用什么来接收,TestAppView吧,

TestAppView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

但是这里怎么样,对,没有导入头文件,在最上面添加导入头文件的代码:

#import “TestAppView.h”

好,那把这个写进来以后,接下来,我们在这里创建好的是不是就是我们那个TestAppView,是不是就是我们这个东西吧,好,创建好的就是这个对象,

TestAppView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

创建好这个对象以后,接下来,我们说,我们这里为什么要自定义View,原因是:

我们是不是希望在这个,是不是希望通过拖线的方式来为这个自定义View增加属性吧,

接下来,注意看,我现在就来给它拖线:

最左边选中这个xib文件,选中中间那一列的Image View,选中里面的这么一个UIView,打开辅助编辑器,一打开辅助编辑器,是不是立刻到了控制器的.h文件里面,也就是ViewController.h文件里面,什么都没有吧,

这样的话,肯定是定位错了吧,定位错了怎么办呢,我们就通过上面那个选择文件的列表,选择TestAppView.h文件,

注意,这个地方,我给它拖线,先给它拖到TestAppView.h文件里面,先给它拖到.h文件里面,

是不是,我在这个TestAppView类的外面,需要访问这个属性,所以需要给它拖到.h文件里面,

如果你拖到.m文件的那个类延展里面,是不是在TestAppView类的外面,就访问不了这个属性了,

好,按住control键,选中Image View,拖线拖到.h文件里面,设置5个参数:

1)第一个参数:Connection:Outlet,

2)第二个参数:Object:不勾选App View,

3)第三个参数:Name:imgViewIcon,

4)第四个参数:Type:UIImageView,

5)第五个参数:Storage:Weak,

点击Connect,

自动生成了一个属性:

#import <UIKit/UIKit.h>

@interface TestAppView:UIView

@property(nonatomic,weak)IBOutlet UIImageView * imgViewIcon;

@end

好,按住control键,选中Label,拖线拖到.h文件里面,设置5个参数:

1)第一个参数:Connection:Outlet,

2)第二个参数:Object:不勾选App View,

3)第三个参数:Name:lblName,

4)第四个参数:Type:UILabel,

5)第五个参数:Storage:Weak,

点击Connect,

自动生成了一个属性:

#import <UIKit/UIKit.h>

@interface TestAppView:UIView

@property(nonatomic,weak)IBOutlet UIImageView * imgViewIcon;

@property(nonatomic,weak)IBOutlet UILabel * lblName;

@end

好,按住control键,选中“下载”按钮,拖线拖到.h文件里面,设置5个参数:

1)第一个参数:Connection:Outlet,

2)第二个参数:Object:不勾选App View,

3)第三个参数:Name:btnDownload,

4)第四个参数:Type:UIButton,

5)第五个参数:Storage:Weak,

点击Connect,

自动生成了一个属性:

#import <UIKit/UIKit.h>

@interface TestAppView:UIView

@property(nonatomic,weak)IBOutlet UIImageView * imgViewIcon;

@property(nonatomic,weak)IBOutlet UILabel * lblName;

@property(nonatomic,weak)IBOutlet UIButton * btnDownload;

@end

这个btnDownload“下载”按钮,我们目前是不是用不到它啊,但是先写上,反正我们目前用不到它,写上也没事儿,

好,这样的话,这里就有3个属性了,

有了3个属性以后,接下来,继续看ViewController.m的代码,

有了3个属性以后,我们在这里,控制器里面,为每个子控件设置数据的时候,还有必要通过Tag来获取这个值吗,没有必要了吧,怎么写,直接这样,

appView,这个控件里面有一个属性,叫什么,imgViewIcon吧,它有一个属性叫什么,image吧,

所以,直接这么写:

appView.imgViewIcon.image = [UIImage imageNamed:appModel.icon];

//是不是直接这么写就ok了,

appView.lblName.text = appModel.name;

//好,这样的话,我们通过这两个属性,就可以给它赋值了吧,

//运行一下,试一试,ok不ok,是不是可以了,

//这样的话,我们就实现了第一步,就是我们这里无需通过Tag的方式,来获取控件,可以给它,可以给这个xib里面的View,指定一个自定义View的一个类,然后通过拖线的方式把xib里面这个View里面的子控件用对应的属性来关联,

//接下来,在外面访问这些子控件的时候,就没有必要通过Tag来访问,可以直接通过这个属性,来访问这个View里面的子控件了,

//这样写,是不是比刚才用Tag写,是不是又好了一点点了,

//但是,这样写,还有一点儿问题,还可以再改进一下,

//那么,这么写的问题,在哪里呢:

appView.imgViewIcon.image = [UIImage imageNamed:appModel.icon];

appView.lblName.text = appModel.name;

//这么写的问题,其实还是和我们之前的字典转模型,和那个有点儿像,

//和那个是怎么回事儿呢,

//咱们之前在字典转模型的时候,还记得咱们封装了一个模型,是不是要给这个模型里面的属性一个一个的赋值吧,

//要给这个模型中的属性,一个一个赋值,

//第一种写法是:

//我在这个模型中,只写了两个属性,然后在这个循环里面,字典转模型的时候,每次创建一个模型,在这个循环里面,为这个模型.属性,是不是通过字典一个一个赋值,

//如果说,这个字典有100个键值对,你在那个地方是不是得写100次代码啊,

//得写100次代码,我们说这样写的问题,是什么问题,你写这个类型,是不是让别人用起来,是不是感觉很不爽啊,

//别人每次用你这个模型,是不是得给它一堆属性赋值,这是第一,你让别人和你合作起来,感觉很不爽,用起来很不方便,

//第二个是,别人给你赋值时候,他怎么知道你这个模型该有哪些属性,是不是他得一个一个查文档,里面有哪些属性,是不是一个一个赋值,

//这样的话,你把过多的东西,暴露给了别人,第一,有时候人家不希望知道这些,反而你暴露给他,他还得给你赋值,是不是有时候比较麻烦,

//第二,再一个就是,你暴露给了别人,有时候会有一些安全性的问题,明白,

//比如说,界面上有一个文本框,我希望你改这个文本框,最好的办法是,你可以给我赋值一个字符串,我去把这个字符串设置给文本框,

//文本框是不是有了这个字符串了吧,

//另外一种办法是:我直接把这个文本框给你,你是不是也能通过这个文本框设置里面的数据,

//大家觉得这两种写法,有区别吗,

//是有区别的:

//你如果第二种办法:你直接把这个文本框暴露给了别人,别人拿到文本框以后,不光可以改文本框里面的文字,可以改文本框的大小,位置,颜色,什么都可以改吧,

//这样的话,是不是你给他,暴露给他,暴露给别人太多东西,是不是别人就可以改了,

//明白我的意思,

//所以,我们这个地方也就是,

//第一,我就不希望告诉你这个控制器,我这个View里面有多少个子控件,

//对吧,出于安全考虑,我不希望告诉你,

//第二,控制器:我也不希望知道你里面有多少个子控件,因为我知道你里面有多少个子控件,是不是还得我来给你设置值啊,

//就是说,有句话叫什么:知道的越多,死的越早吧,

//还有另一句话就是:你知道的越多,你的责任是不是就越大,

//有些人,为什么没心没肺那些人活的时间长呢,知道的少吧,想的少,所以活的时间长,

//像你这个,知道的越多,你得给它赋值,这样的话,如果有100个地方使用这句话,使用这个自定义View,是不是100个地方都得手动给它赋值:

appView.imgViewIcon.image = [UIImage imageNamed:appModel.icon];

appView.lblName.text = appModel.name;

所以说,为了解决这个问题,我们要把这个代码,再给它封装一下,

回忆一下,我们之前字典转模型的时候,那个时候是怎么封装的呢,

是不是在那个模型里面,传了一个字典对象过去,然后,在模型的内部,解析字典,把字典中的每一个键值对,赋值给了模型的属性,

我们这里也采取类似的思路,

我这里拿到这个自定义View以后,xib这个View以后,

//1.通过xib创建每个应用(UIView)

//通过动态加载xib文件创建里面的View

//1.1> 找到应用的根目录

NSBundel *rootBundle = [NSBundel mainBundle];

//1.2> 在应用程序根目录下去搜索对应的xib(nib)文件

TestAppView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

//接下来,你数据不是在这个模型里面吗,

//获取当前这个应用的数据模型

TestApp *appModel = self.apps[i];

//我希望你直接把这个模型,传递给我这个自定义View,然后在自定义View里面,根据模型,把模型中的数据,解析、设置给这个View的子控件,

//那么,如果这么写的话,控制器的作用,就非常简单了,

//控制器里面要干什么呢:

//1)控制器的第一步:把数据得到,

//获取当前这个应用的数据模型

TestApp *appModel = self.apps[i];

//2)控制器的第二步:创建View,

//1.通过xib创建每个应用(UIView)

//通过动态加载xib文件创建里面的View

//1.1> 找到应用的根目录

NSBundel *rootBundle = [NSBundel mainBundle];

//1.2> 在应用程序根目录下去搜索对应的xib(nib)文件

TestAppView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

//3)控制器的第三步:把数据模型给了View,是不是让它俩结合,

//然后,在View的内部,是不是View的内部会解析这个数据模型,设置给它的子控件吧,

//相当于,设置数据,我也不这么写了:

appView.imgViewIcon.image = [UIImage imageNamed:appModel.icon];

appView.lblName.text = appModel.name;

//appView.model , appView点比如说有一个叫做model这个属性,或者app属性,就叫model吧,

appView.model =

//把谁赋值给它,把上面这个,这是不是模型数据啊:

TestApp *appModel = self.apps[i];

//把这个模型对象,赋值给appView的这个model属性,它现在是不是没有这个属性啊,

//我们假设它有这个属性,假设它有这个model属性,

//然后,这样的话,我们控制器里面,只做三件事儿:

//1)第一,根据索引拿到当前这个应用的模型数据,

TestApp *appModel = self.apps[i];

//2)第二,创建这个应用的这个View,View是根据xib创建的,是不是View里面的子控件都有了,

NSBundle *rootBundle = [NSBundel mainBundle];

TestAppView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

//3)第三,直接让它俩结合,怎么让它俩结合,

//让这个appView,给它自己内部写一个属性,让这个模型,等于你刚才拿到的模型对象,

//这样的话,是不是让它俩结合了,

//它俩一结合,在内部,就会把模型中的数据取出来,设置给这个View里面的各个子控件,

//对于控制器来说,它是不是不需要了解太多吧,

//它只负责,把模型拿过来,把View拿过来,它俩一结合,是不是数据,界面上就有了吧,

//这样的话,我们模型里面的任务,就会非常非常的清晰,

//所以说,接下来,我们就把这个代码,封装一下,不这么写:

appView.imgViewIcon.image = [UIImage imageNamed:appModel.icon];

appView.lblName.text = appModel.name;

//既然,你这里不这么写了,所以说,你还有必要在外面,在控制器里面去访问这个View里面的子控件吗,没有了吧,

//所以说,我们在这里,就可以,怎么办

//把TestAppView.h里面的三个拖线生成的属性,给它剪切到TestAppView.m文件里面吧,

//在TestAppView.h文件中:

#import <UIKit/UIKit.h>

@property(weak,nonatomic) IBOutlet UIImageView *imgViewIcon;

@property(weak,nonatomic) IBOutlet UILabel *lblName;

@property(weak,nonatomic) IBOutlet UIButton *btnDownload;

@end

//把这三个属性,剪切到TestAppView.m文件里面,

//剪切到.m文件里,得有个什么,是不是得有个延展啊:

//在TestAppView.m文件中:

#import “TestAppView.h”

@interface TestAppView ()

@property(weak,nonatomic) IBOutlet UIImageView *imgViewIcon;

@property(weak,nonatomic) IBOutlet UILabel *lblName;

@property(weak,nonatomic) IBOutlet UIButton *btnDownload;

@end

@implementation TestAppView

@end

//ok,把这个剪切到.m文件里面,

//因为,如果说,这个和界面上的控件相关联的属性,如果在.h文件里面的话,是不是外部是能访问到的,

//如果外部能访问到那几个控件以后,谁告诉你,它只给你设置数据啊,

//一旦他在控制器里面,把这几个控件的位置也改了,怎么办,把它大小也改了,怎么办,

//这些是不是都有可能,一些潜在的隐患啊,

//有人说,那他手那么贱吗,他为什么要改啊,

//咱们现在的程序,是一个小程序,都是咱们自己写的,是不是你说不敢这么改啊

//将来咱们的程序是个大程序了,比如说“新浪微博”,你给外界开发一个接口,让所有开发者调用这个接口来访问,你能保证每一个开发者都那么规矩吗,每一个开发者都那么听话吗,

//有些人,拿到别人的接口以后,他就喜欢测试,找到一个Bug,他觉得好开心,

//所以说,你绝对不能把这些东西都暴露给别人,

//所以说,出于各种考虑,我们就把这个引用界面上控件的一些属性,把它拖线,拖到.m文件里面,不是在.h文件里面,

//这样的话,在外界,是不是访问不到这些属性了吧,

//换句话说,在外界,你就无法去访问到这个View里面的子控件了,

//然后,接下来,怎么办呢,你是不是拿到这个模型,希望给我这个自定义View设置一个模型属性吧,

//但是,大家看,我这个自定义View里面,有那个模型属性吗,没有吧,

//接下来,怎么办,给它增加一个是不是就ok了,那个模型叫啥,TestApp吧,

//在TestAppView.h文件中:

#import <UIKit/UIKit.h>

@interface TestAppView:UIView

@property(nonatomic,strong) TestApp *model;

@end

//请问这里,能访问这个类吗,访问不到吧,

//得加一个@class TestApp;

//吧,

#import <UIKit/UIKit.h>

@class TestApp;

@interface TestAppView:UIView

@property(nonatomic,strong) TestApp *model;

@end

//注意,在这里,只写了这个模型,然后呢,注意看,

//当我执行这句话的时候:

appView.model = appModel;

//其实,是调用了这个模型这个属性的setter方法吧,

//也就是说,我希望,当它一调setter方法的时候,立即把这个模型取出来,把模型中的数据取出来,设置给子控件,

//所以说,我这里就重写这个属性的setter方法,

//在这个setter方法里面,把这个模型中的数据,解析,设置给子控件,

//是不是就ok了,

//在TestAppView.m文件中:

#import “TestAppView.h”

#import “TestApp.h”

@interface TestAppView ()

@property(weak,nonatomic) IBOutlet UIImageView *imgViewIcon;

@property(weak,nonatomic) IBOutlet UILabel *lblName;

@property(weak,nonatomic) IBOutlet UIButton *btnDownload;

@end

@implementation TestAppView

//重写model属性的setter方法

- (void)setModel:(TestApp *)model{

//在里面,注意,当你重写setter方法的时候,不管里面有什么样的代码,先把这句代码写上:

// 先把赋值代码写上:

_model = model;

//如果你不赋值,后面就有可能会忘了,

//然后,接下来,就干什么,

//解析模型数据,把模型数据赋值给UIView中的各个子控件,

self.imgViewIcon.image = [UIImage imageNamed:model.icon];

self.lblName.text = model.name;

//ok,这样是不是就是解析了吧,这样就实现解析了,

//有了这个属性以后,接下里,你控制器里面,需要做的就是:

//1)第一,创建View,

NSBundle *rootBundle = [NSBundle mainBundle];

TestAppView *appView = [[rootBundle loadNibNamed:@“TestAppView” owner:nil options:nil] lastObject];

//2)第二,创建好View以后,直接把这个模型数据,给了View,它内部是不是帮你进行设置的数据啊,

//运行一下试试,是不是依然是可以的,

//这就是我们这里的程序封装,

//之前给大家介绍了一个什么内容,为了不让我们控制器这个for循环中有一堆自定义子控件的这些代码,所以说我们通过xib来抽象一些控件吧,

//通过xib,在xib里面拖鼠标,拖、拉、拽的方式,拽了一个View,及里面的子控件,

//然后,接下来,我们在这里(ViewController.m文件中),通过加载xib的方式,创建了这么一个View,

//然后呢,设置View里面子控件的数据,设置View里面子控件的数据的时候:因为这样写,封装性不好,这样写,会给外界暴露太多的数据,

//这样写,依赖于View里面的Tag,等等,这一系列原因,

//所以说,我们在这个自定义View里面,通过拖线的方式,是不是用3个属性,来描述界面上的3个控件,

//然后呢,在这个自定义View里面,写了一个模型这个属性:

//在TestAppView.h文件中

#import <UIKit/UIKit.h>

@class TestApp;

@interface TestAppView:UIView

@property(nonatomic,strong)TestApp *model;

@end

//接下来,我们在控制器里面,只要把模型传递给自定义View里面模型这个属性,在setter方法内部,是不是实现了一个赋值啊:

//在TestAppView.m文件中:

//重写model属性的setter方法

- (void)setModel:(TestApp *)model{

//先赋值

_model = model;

//解析模型数据,把模型数据赋值给UIView中的各个子控件

self.imgViewIcon.image = [UIImage imageNamed:model.icon];

self.lblName.text = model.name;

}

//这是不是就是刚才做的内容,

//这么写的话,大家有没有感觉到,其实整体上就分三大部分:

//1)第一部分:模型数据吧,

//2)第二部分:是用来展示界面的一个视图,

//3)第三部分:是整体的这个控制器,

//也就是说,第一,在控制器里面,负责从模型中取出数据,控制器负责从模型中取出数据,

//第二,控制器负责拿到界面View,

//第三,控制器负责让它俩结合,

//最后,是不是显示到我们的界面上,

//所以说,这时候,我们可以看到一个Model,是一个模型,

//View,就是我们这个界面,UIView,

//Controller,就是我们的控制器,

//控制器,是不是干的活儿最多的吧,控制器干的活儿最多,

//模型,相对来说最简单,只要保存数据,

//View,它只负责展示数据,采集数据,

//这样的话,就可以大致上先有一个MVC这么一个概念,

//好了,这就是我们这里讲的封装xib

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清风清晨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值