一、我们先把九宫格应用管理的内容再给大家过一下: 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个啊, 那么,为什么这个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里面的子控件设置具体的数据啊, 所以,接下来,第二步,是给子控件设置数据吧, 三、给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