iOS狂暴之路---视图控制器(UIViewController)使用详解
pdft7146
8年前
<h2>一、前言</h2> <p>在之前的一片文章中已经介绍了 从iOS的第一个应用中能学习到哪些知识点 在那篇文章中主要介绍了一个iOS程序的启动过程和应用的几大对象,以及应用的生命周期,同时也介绍了应用中的控制器知识点,介绍了其生命周期方法,那么对于一个iOS应用一般都是会包含多个页面,而每个页面就是一个控制器,一个控制器一般都是关系到一个UIView的,但是我们在真正使用这些控制器的时候会发现,多个页面之间的跳转关系该如何控制。在之前的文章知道一个应用对应一个窗口对象UIWindow,每个窗口都有一个根控制器对象,那么如果一个应用有多个控制器该如何管理这些控制器呢?那么就是本文需要介绍的重点了。</p> <h2>二、两个视图控制器</h2> <p>在Android中我们知道每个页面都是一个Activity,每个页面之间的跳转以及通信都是采用Intent对象进行传递的,那么在iOS中并没有这种机制了,而在iOS中管理多个控制器一般都是两种控制器:</p> <p>一种是切换控制器UITabBarController,一种是导航控制器UINavigationController</p> <p>这两种控制器虽然是管理多个控制器,但是他们两本身也是个控制器类,而且他们两个都有各自的使用场景。</p> <p>1、UITabBarController一般用于首页中的页面切换,比如微信的首页中四个Tab切换就是采用这个控制器管理的:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/20186e1c4b5ff257bb6ae548df2f1349.png"></p> <p>这个有点类似于Android中的ViewPager+Fragment实现的功能。</p> <p>2、UINavigationController一般用于从一个控制器页面跳转到另外一个页面控制器,这个就和Android中的Activity跳转非常相似了。而且也是使用场景最多的一个了。</p> <h2>三、切换控制器UITabBarController</h2> <p>下面先来介绍第一个控制器管理类:UITabBarController类,首先不多说,还是老样子,先建立一个简单的案例:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/113c577396767c92aa6569dbe15314f6.png"></p> <p>选择需要继承的父类UITabBarController,其实他也是一个控制器:</p> <p><img src="https://simg.open-open.com/show/b610b5b11df876ad535a67201759c17a.png"></p> <p>那下面我们需要把这个控制器设置成根控制器,然后在通过这个控制器来管理后续添加的子控制器内容:</p> <p><img src="https://simg.open-open.com/show/ee4b0db776a2978844dbee76b9194f72.png"></p> <p>在AppDelegate回调方法中和之前一样的方式设置根控制器,下面就来开始添加子控制器了:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/60f0bdf44fde122460238510a77ba12f.png"></p> <p>因为需要多个子控制器进行操作案例,所以这里就新建了两个控制器类,然后初始化之后记得设置子控制器的tabBarItem属性,这个属性代表着这个子控制器在UITabBarController的item样式属性。当然这里为了简单就采用系统提供的一些样式了,也可以自定义自己的样式的,这个后面等介绍具体项目的时候再说,其实不难也是一些属性的使用罢了。初始化完成之后就把所有的子控制器添加到一个NSArray中,最后在设置到UITabBarController中即可。下面来看一下运行效果:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/acf9a1b53d20b21a3da36a0b4accc0ac.png"></p> <p>看到底部有两个可以切换的item,看到他们的样式就是系统对应的联系人和更多的样式,当然我们可以自定义这样的样式,可以设置item的图片和文字。这里就不演示了。</p> <p>到这里我们会发现这个控制器真的和Android中的ViewPager+Fragment非常相似,那么问题来了,我们在Android中使用ViewPager+Fragment进行开发的时候,仅仅的简单添加子控制器是满足不了需求的,我们一般还需要知道一些事,这里主要是两件事:</p> <p>第一件事:每个子控制器之间的切换事件,也就是tab切换的回调事件</p> <p>第二件事:在切换的过程中每个子控制器的生命周期会发什么变化</p> <p>那么下面就在来详细分析一下这两件事,先来看第一件事:如何监听每个子控制器之间的切换事件</p> <p>这个和Android中也非常类似的,就是给切换控制器添加代理方法,当然在Android中叫做回调方法。这里添加代理非常简单:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/d33b43bfd61579d35ea01a020e27c876.png"></p> <p>因为我们在AppDelegate类中定义了控制器,所以就需要AppDelegate类实现代理协议了:UITabBarControllerDelegate,实现之后我们就可以实现几个代理方法了:</p> <p>第一个方法:这个代理方法是在子控制器切换完成之后调用,参数传回来的是当前选中的子控制器。</p> <p>- ( void )tabBarController:( UITabBarController *)tabBarController didSelectViewController:( UIViewController *)viewController</p> <p>第二个方法:这个代理方法是决定当前子控制器是否可以被选择,如果返回YES表示可以选中,如果返回NO表示不可选中,也就是不可切换操作了。</p> <p>- ( BOOL )tabBarController:( UITabBarController *)tabBarController shouldSelectViewController:( UIViewController *)viewController</p> <p>当然还有其他代理方法,这里就不一一详细介绍了,因为这两个方法在实际开发中用到的最多。这里顺便再来讲一下这个控制器的两个常用的属性:</p> <p>第一个属性:selectedIndex代表的是获取到当前选中的子控制器的索引值,这个属性可以读写操作,也就是可以设置索引值来决定当前哪个控制器被选中,一般是在初始化的时候会决定当前哪个控制器被默认选中。</p> <p>第二个属性:selectedViewController代表当前选中的子控制器,这个属性也是可以读写操作的,其实他的功能和上面的索引值功能差不多,只是一个是操作子控制器索引值,一个是直接操作子控制器对象的。</p> <p>有了这两个属性在结合上面的那两个代理方法就可以满足我们的开发需求了。</p> <p>下面在来解决第二件事,就是各个子控制器在切换过程中他们的生命周期会发什么变化,其实我们在前面一篇文章中已经介绍了控制器的生命周期方法了:</p> <p>1> view初始化完毕后,就会调用控制器的viewDidLoad方法<br> 2> view初始化完毕后,就会把这个根控制器的view添加到窗口中<br> 3> 当view即将被添加到窗口中时,就会调用控制器的viewWillAppear:方法<br> 4> 当view已经被添加到窗口中时,就会调用控制器的viewDidAppear:方法<br> 5> 如果控制器的view即将从窗口中移除时,就会调用控制器的viewWillDisappear:方法<br> 6> 如果控制器的view已经从窗口中移除时,就会调用控制器的viewDidDisappear:方法<br> 7> 如果控制器接收到内存警告的时候,就会调用控制器的didReceiveMemoryWarning方法<br> didReceiveMemoryWarning方法的默认实现是:如果控制器的view没有显示在窗口中,也就是说controller.view.superview为nil时,系统就会销毁控制器的view.<br> 8> 销毁完毕后会调用控制器的viewDidUnload方法<br> 9> 如果控制器的view以前因为内存警告被销毁过,现在需要再次访问控制器的view时,会重复前面的步骤初始化view</p> <p>这里我们主要是来看一下viewWillAppear,viewDidAppear,viewWillDisapper,viewDidDisappear方法,其他方法这里可能用不到了。我们为了方便查看效果,可以定义一个BaseViewController类,然后在其生命周期方法中添加日志信息,最后让子控制器都继承这个类,这样每个子控制器的生命周期方法都可以发现了:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6bcc931288f85fc58bbffadb610a7f7d.png"></p> <p>这里看到我们是如何添加日志的信息的,首先需要知道是哪个子控制器所以需要打印子控制器名称,可以使用self关键字,然后就是方法名称了,这里因为不想手动的去写方法名,所以就用NSStringFromSelector(_cmd)来获取当前方法的名称。</p> <p>下面我们再次运行程序,然后多次切换子控制器看看效果:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/a59deae3c84ef7419437ae76216dd261.png"></p> <p>从日志中我们可以得到三个重要信息:</p> <p>第一个信息:当一个子控制器只有当要被显示的时候才会调用viewDidLoad代理方法,看到开始的时候,第二个子控制器的这个方法并没有调动,当我切换到第二个控制器的时候进行展示才调用了,可以理解为懒加载机制。用到才进行加载view。</p> <p>第二个信息:每个子控制器的视图加载代理方法vieDidLoad只会调用一次,也就是第一次展示的时候,后面再次展示就不会再次调用了,当然这个不是绝对的,比如如果这时候系统内存不足,会回收一些资源,那么这时候可能会把子控制器进行回收,那么下次再次切换到这个子控制器的时候还是会调用他的加载方法,但是大部分情况下都只调用一次。</p> <p>第三个信息:也就是我们最关心的信息,就是每个子控制器在来回切换的过程中会回调viewDidAppear,viewDidDisAppear等方法。所以如果我们需要做一些操作就要在这两个代理方法中进行了。</p> <p>到这里我们就算介绍完了iOS中的第一个控制器管理类UITabBarController,其实他和Android中的ViewPager+Fragment非常类似,我们从在Android中的使用需求可以在iOS中得到我们在实际开发使用中想要得到的一些信息,这里一般就是两件事:第一件事就是切换的回调也就是代理方法,第二件事就是在切换过程中各个子控制器的生命周期发生了如何变化。最后就是几个重要的属性,比如如何得到当前切换到哪个子控制器了,如何手动的设置到默认的选择到哪个子控制器上等。有了这些信息我们就可以满足正常的程序开发了。那么接下来我们还要来介绍一个控制器管理类,这个类在实际开发中用的就比较多了。</p> <h2>四、导航控制器UINavigationController</h2> <p>导航控制器UINavigationController类在实际开发过程中用到的可能比较多,一般从一个页面跳转到另外一个页面就需要用这个控制器了,我们还是和上面的步骤一样,开始的时候简单的新建一个这个控制器,记得需要继承UINavigationController类:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/8f8478425556e8426c5a54011d07a573.png"></p> <p>他其实也是一个控制器:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/8cf70e169559c676315b2cfc07380d12.png"></p> <p>定义好之后,咋们就可以在AppDelegate回调方法中设置应用窗口控制器的根控制器类:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/817e37be6ec7034e72a137bf3dc34af2.png"></p> <p>这里和UITabBarController有个区别,这个控制器其实和Android的Activity非常类似,因为这里也是采用栈的结构,在Android中所有的Activity有一个栈结构维护的。但是这里比Android简单,就是没有那么多复杂的启动模式啥的,只要记得是用栈结构来维护应用中的控制器即可。那么关于栈的操作就是出栈和入栈,而这里的栈顶的控制器是展示在当前应用中的,所以如果我们想进入到某个页面,那么只需要把这个控制器页面入栈即可,如果要返回就直接出栈即可。</p> <p>下面咋们还是用上面那两个子控制器作为案例进行操作,开始的时候咋们把第一个子控制器进行入栈进行展示:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/cc43aa28a0e294480fa75c3292c75242.png"></p> <p>看到顶部有两个选项,其实这个是导航控制器对于每个子控制器的一个导航item的标题设置,这个item一般包括标题,左边item,右边item,而这些item就和之前的tabitem类似,有icon和文字。这里我们把右边的item添加点击事件,点击之后就跳转到了第二个控制器,在第二个控制器的导航item中添加左边item点击事件,点击就返回:</p> <p><img src="https://simg.open-open.com/show/67970c9047e94d7f9803c545e2c0ce4f.png"></p> <p>在第一个子控制器中,我们设置了导航item的标题内容,左右item内容,这里依旧采用了系统的样式,然后添加了点击事件,而在点击之后跳转到第二个子控制器也是直接采用入栈操作即可,这里需要注意的是,对于每个子控制器都可以使用navigationController属性来获取他的导航控制器对象,然后就可以操作栈了。从这里也可以看到,在第一个控制器中肯定用到了第二个控制器所以需要导入类定义,同时我们一般在跳转的时候需要携带数据的,那么这里就可以直接通过第二个控制器对象的一些方法设置即可。这个和Android中不一样了,可以把数据捆绑到Bundle对象打包发过去了。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/93f178ad9b448c45b15b565b59867da0.png"></p> <p>在第二个子控制器中,我们没有定义导航item了,但是我们还是看到了,这个是系统默认就有的效果,当然我们也可以不要,但是一般都会保留的。当然我们自己也模拟了点击返回的效果:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/f702f7c709fa802739ad1f479319a12c.png"></p> <p>我们这里可以看到导航控制器提供了三个方式出栈的方法,下面来看一下他们三个的区别:</p> <p>1、popViewControllerAnimated:</p> <p>这个方法是我们用的最多的,就是直接出栈操作,相当于删除栈顶对象,那么就有了返回的效果了。</p> <p>2、popToRootViewControllerAnimated:</p> <p>这个方法看名称可以知道也是出栈,但是他出的非常彻底,直接回到了栈底,把栈里的子控制器都出栈了。</p> <p>3、popToViewController:animated:</p> <p>这个方法看多了个参数,也就说可以出栈到指定的控制器那个位置,就是指定的控制器之前的子控制器都得出栈。</p> <p>其实看到这三种方式和Android中的Activity的启动模式非常相似。</p> <p>第一个方法对应的是Android中的singleTop启动模式</p> <p>第二个方法对应的是Android中的singleTask启动模式</p> <p>第三个方法对应的是Android中的singleInstance启动模式</p> <p>当然这里只是为了好理解,就和Android中作比较,可以发现也并不是完全一致的。不过这三种出栈方式也是非常好理解的,因为他们三个方法正好能够满足我们开发中所有的出栈的需求了。</p> <p>再看一下后面的一段代码,执行了一个代理对象方法,其实这个就是做了当前子控制器返回之后需要携带一些返回数据给上一个子控制器的功能, 那为什么这里不在这个子控制器类中导入第一个子控制器定义,然后直接调用其方法得到返回数据呢?其实想想应该不能这么做,因为我们知道从一个控制器跳转到下一个控制器只有一条路走。所以导入是没有关系的,但是如果从一个控制器返回到上一个控制器就有多条路了,因为这个控制器可能由多个控制器跳转过来的,那么如果都需要返回值,就需要导入每个跳转过来的控制器定义了,可想而知这个子控制器类会变得非常庞大和耦合。所以这里我们可以在第二个控制器中定义一个协议,所有需要跳转过来的控制器都可以实现这个协议,然后在第二个控制器中就可以非常的灵活调用这个id类型对象的指定方法,实现返回值功能了。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/01420451b3a8f46a595ca49d94334968.png"></p> <p>在返回的时候,首先判断一下代理对象有没有对应的代理方法,有的话就开始调用即可。</p> <p>和之前的切换控制器一样,我们在使用导航控制器也是需要解决两件事:</p> <p>第一件事:子控制器在入栈和出栈的代理方法</p> <p>第二件事:子控制器在入栈和出栈的时候自身的生命周期变化</p> <p>下面来看一下第一件事,也是和之前一样,这里我们也是需要实现一个协议:UINavigationControllerDelegate,还是AppDelegate类需要实现的。</p> <p>第一个方法:这个代理方法的功能是即将要展示哪个控制器了,也就是入栈操作了。</p> <p>- ( void )navigationController:( UINavigationController *)navigationController willShowViewController:( UIViewController *)viewController animated:( BOOL )animated</p> <p>第二个方法:这个代理方法的功能是哪个控制器进行展示了,也就是栈顶控制器。</p> <p>- ( void )navigationController:( UINavigationController *)navigationController didShowViewController:( UIViewController *)viewController animated:( BOOL )animated</p> <p>所以从这两个方法中可以看到,这些代理方法其实就是监听栈顶变化的,如果栈顶控制器发生变化之后就会回调方法。</p> <p>下面继续来看第二件事,因为我们沿用了上面的子控制器,所以日志都加好了,咋们还是直接来回切换几次看看效果:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/e8dd6c8850589e9c58d2c4bd15912881.png"></p> <p>从生命周期上可以看到,和上面的切换控制器一样,各个子控制器都是采用懒加载机制,用到展示的采取进行加载,以后只会调用viewDidDisappear和viewDidAppear等方法了。这里从日志也可以看到我们可以正确的获取到从上个子控制器中传过来的数据,也可以获取到前一个子控制器返回的数据。</p> <h2>五、视图控制器总结</h2> <p>到这里我们就介绍完了iOS中常用的两个控制器管理类,也就是一个应用中多个控制器之间的跳转切换等效果。下面来总结一下:</p> <p>第一个:切换控制器UITabBarController</p> <p>这个控制器一般用于首页的切换tab功能,比如微信的那样的效果,他和Android中的ViewPager+Fragment组合使用效果非常类似,在使用的过程中,需要使用一个子控制器数组存放所有的子控制器。然后每个子控制器之间的切换操作有对应的回调代理方法,</p> <p>1、监听到切换到哪个子控制器</p> <p>2、可以指定返回值来设置哪个子控制器不可切换选择</p> <p>在这个过程中每个子控制器的生命周期方法是:</p> <p>1、所有子控制器都是采用懒加载机制,需要展示的时候才去加载</p> <p>2、如果已经加载过得子控制器下次再次切换的时候只会调用viewDidAppear和viewDidDisapper等方法</p> <p>最后就是有两个重要属性:</p> <p>1、第一个属性是当前选择的子控制器的索引值,这个值可以读写,通过这个值可以设置默认选择哪个子控制器</p> <p>2、第二个属性是当前选择的子控制器对象,这个值可以读写</p> <p>对于每个子控制器都有一个属性:tabBarController 可以获取到当前的切换控制器对象。</p> <p>第二个:导航控制器UINavigationController</p> <p>这个控制器用的比较多,一般用于程序的多个子控制器之间跳转,这个控制器有一个特殊的地方就是他采用的是栈结构来管理子控制器,这一点和Android中的Activity非常类似,也是采用栈结构。那么对于跳转就是入栈操作,返回就是出栈操作。操作也是非常简单的。同样的这里我们在操作的时候也是有两个件事需要知道,一个是各个子控制器在跳转的时候的回调代理方法:</p> <p>1、监听当前栈顶变化的回调代理方法</p> <p>还有一个就是需要知道每个子控制器的生命周期方法变化:</p> <p>1、所有子控制器都是采用懒加载机制,需要展示的时候才去加载。</p> <p>2、如果已经加载过得子控制器下次再次切换的时候只会调用viewDidAppear和viewDidDisapper等方法</p> <p>然后需要注意的是在出栈的时候有三种方式:</p> <p>1>、popViewControllerAnimated:</p> <p>这个方法是我们用的最多的,就是直接出栈操作,相当于删除栈顶对象,那么就有了返回的效果了。</p> <p>2>、popToRootViewControllerAnimated:</p> <p>这个方法看名称可以知道也是出栈,但是他出的非常彻底,直接回到了栈底,把栈里的子控制器都出栈了。</p> <p>3>、popToViewController:animated:</p> <p>这个方法看多了个参数,也就说可以出栈到指定的控制器那个位置,就是指定的控制器之前的子控制器都得出栈。</p> <p>而这三种方式和Android中的启动模式都有对应的方式,有了这三种方式就可以满足我们日常中开发需要了。</p> <p>最后是每个子控制器都可以通过navigationController属性获取到导航控制器对象</p> <h2>七、总结</h2> <p>本文就介绍完了iOS中的控制器管理类,主要是用来解决一个应用中多个子控制器之间的切换和跳转问题,在iOS中的操作非常简单,不想Android中那么复杂。有了这个知识之后后面我们就可以简单的开发一个应用了,这个应用可以包含多个控制器页面了,然后需要做的是每个控制器展示的内容,也就是对应的具体UIView了。这个将是我们后面文章需要详细介绍的内容了。</p> <p> </p> <h2> </h2> <p> </p> <p>来自:http://blog.csdn.net/jiangwei0910410003/article/details/52971397</p> <p> </p>