Containing ViewControllers
Containing ViewControllers
转自:https://www.cocoanetics.com/2012/04/containing-viewcontrollers/
For a project that I am currently working on I needed to implement a custom container view controller. I was feeling my way forward in the dark for the most part because this does not seem to be a widely used technique. Developers – understandably – favor reusing and skinning existing view controllers over creating new containers.
However there are some scenarios where you should prefer to make your own container because it greatly simplifies your code over trying to bend a UINavigationController or UITabBarController to your will. Do you remember the times when those two where the only two containers available?
I distinctly remember using a UINavigationController with hidden nav bar as a view controller to contain multiple full views. And you probably had do do your own view juggling and animations because for the most part the standard transitions would be useless. Fortunately we can no file this as a fond memory of the past and move on to implementing our own containers.
The first thing to wrap your head around is the notion that besides a hierarchy of views you now also have to have a consistent hierarchy of view controllers. Before iOS 5 people would often create a new view controller and then slap this VC’s view into an existing view hierarchy. Now more!
Nowadays you never would resort to this. Instead you use the mechanisms afforded by UIViewController to add and remove child view controllers.
Another notion is that we’ve trained ourselves to think of view controllers as being responsible for one entire full screen, think of the sub view controllers of a tab bar controller. But ever since UISplitViewController which arrived with the iPad this is no longer the true mantra. Viewcontrollers are supposed to manage a coherent region on the screen, that can be the entire screen for lack of space on an iPhone, but it can also be a content bar at one of the sides of the screen. Like UISplitViewController which has two sub-viewcontrollers, one for the left (“master”) panel and one for the right (“detail”) panel.
UIViewController provides two methods to add a view controller as a child and to remove it later. Those are part of the “UIContainerViewControllerProtectedMethods” category extension for UIViewController:
@interface UIViewController (UIContainerViewControllerProtectedMethods) |
These two methods do exactly what their names suggest. Though what’s not quite obvious is how you are supposed to use them. And if and how you should combine these with addSubview and removeFromSuperview. Hence this exploration. Note: All this assumes we use ARC.
According to the docs it is up to us to define the kinds of relationships we want to model. Be it only a single VC visible at the time like nav controllers, or multiple that can be reached through tabs. Or even multiple VCs that are sort of like pages.
There are three possible things that you want to be able to do with your sub-viewcontrollers that have slightly different semantics:
- Add it to your container
- Remove it from your container
- Transition to another view controller (i.e. add the new and remove the old)
For any of these you also want to be be assured that the 4 view delegate methods get properly called, as well as the new 2 delegate methods that fire before and after a VC moved to a new parent. Note that a parent of nil means that it was removed.
Why is this attention to the delegate messaging necessary? You probably use the view(Did|Will)(A|Disa)pear methods to do some last setup or teardown and so you are interested that they get properly called. And also there is an ugly warning in the console about unbalanced messages if you get something wrong here.
We’ll dive into greater detail with a sample. Let’s say we want to get an effect similar to a tabbed view controller. i.e. we have an array of view controllers and we want to switch between these. Since this container VC will be our app’s root view controller we want to show the first sub-VC when it shows.
Basic Setup
Let’s put the necessary bare bones IVARs in the implementation because we only need to have access to these inside our own ContainerViewController.
@implementation ContainerViewController |
The subVCs will be a static array of view controllers that the developer can set. The selected VC will hold a reference to the currently showing VC and the container view will be the area where we want our sub VC’s view to be positioned. Let’s start by setting up the container view in the container’s loadView:
- (void)loadView |
I’ve colored the main view blue and the container view red for clarity. The sub-VCs will go in the red area and be automatically resized if we rotate the device.
The integration in the app delegate is a mere formality, add the import for the header, allocate an instance and set it as root VC.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions |
Next we need a couple of view controllers to play the role of the sub-VCs. Let’s create a simple UIViewController subclass that has a UILabel as it’s main view, so that we can display something there to tell them apart. Do avoid overcomplicating things for this example we simple take the view’s own description in there. This way we see if the display changes.
- (void)loadView |
I bet you never before had a view controller which consisted only of a UILabel. For Science!
Adding
Next we need to put a couple of these PageViewControllers into an array and have a method that allows us to set this array into our container. Let’s assume that you know how to create a property for the subViewControllers. In the app delegate we have these additional lines:
// make an array of 5 PageVCs |
So much for basic setup. Now let’s override the setter for the sub VCs to select the VC at index 0 and present it. Though we cannot present it in the setter, because the view might not have been loaded yet and so our _containerView IVAR is still nil.
- (void)setSubViewControllers:(NSArray *)subViewControllers |
Instead we do it at the latest possible moment, that would be in the viewWillAppear because here we are guaranteed that the loadView has taken place already. A nice lazy thing, if we find that the selected VC already has self as the parent then there’s nothing to do.
- (void)viewWillAppear:(BOOL)animated |
The above shown sequence takes care of calling the viewWillAppear, viewDidAppear, willMoveToParentViewController and didMoveToParentViewController. Notice that all but the last are done for you automatically, but for some strange reason the didMove is not. So we have to do that manually. Upon starting our Demo we now see the VC at index 0.
Next we add the capability to transition from one VC to the next.
Transitioning
To move between the child VCs we’ll add a swipe gesture recognizer to our container. If we swipe left we want to go to the VC with a lower index in the array. If we swipe right we want to go higher. No wrapping. In loadView we add:
// add gesture support |
And the implementation of swipe is as follows. For simplicity’s sake we use two separate gesture recognizers because Apple does not provide an easy way to determine the swipe direction if you combine to directions.
- (void)swipeLeft:(UISwipeGestureRecognizer *)gesture |
The logic to transition from one VC to another we package in transitionFromViewController:toViewController:. This is the really interesting part. There is a convenient method that again takes care of most of the boring work of adding and removing views. And again some non-obvious additional messaging is necessary to get it perfect.
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController |
And with that we have our transitions in the can.
You have a number of UIViewAnimationOptionTransition-s available, but you don’t have to settle for these. You can also pass 0 for the options and instead provide all animations you like the two views to execute in the animations block.
Before I discovered is method I was using the previous method of animating the views. Though this has a side-effect that we might not like in this case. Typically you want the “will” delegate methods to fire before the transition and the “did” to follow afterwards. If you animate the views yourself then iOS 5 will take care of sending these messages for you but it does so together. This looses us the ability to differentiate between stuff we want to do before and after the appearing and disappearing of the view controller.
Conclusion
It took me quite a bit of experimenting to also get all the messaging happening and equally balanced. The above sample has that working as it should.
But once you do figure out the two techniques outlined in this article you are on the road to implement your own view controller containment like you never did anything else.
One thing that I wasn’t able to figure out so far is why the transition method always adds the new view controller’s view to the container view controller’s main view. This simplifes the process somewhat because you don’t have to know at which stage in the transition it is ok to add and remove the views. But at the same time you might have a situation where you don’t want the animation to occur over the entire area of the container view controller.
For this scenario I can only think of covering up the parts with extra sub views. Or we could stack multiple container view controllers and have one to only cover the region where where have the container view. This could then clip its subviews and thus be only care for this area.
The main advantage of any kind of view controller containment is that rotation messages (should|will|did) reach the lowest leaves of your view controller tree. Unless you disable that by means of overriding automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers and returning NO.
But who would really want that when we have waited for so long for public API to have these being forwarded? Working with view controller containers I get a feeling that they greatly simplified creating complex user interfaces consisting of multiple parts.
The project for this tutorial is in my Examples GitHub repository.
Containing ViewControllers的更多相关文章
- 【翻译】在Ext JS 5种使用ViewControllers
原文:Using ViewControllers in Ext JS 5 简单介绍 在Ext JS 5中,在应用程序架构方面提供了一些令人兴奋的改进,如加入了ViewModels.MVVM以及view ...
- 轻量化ViewControllers,读文章做的总结
推荐一个网站 http://objccn.io/ 我这两天才开始看 获益匪浅 看了第一篇文章 <更轻量的View Controllers>感觉写的不错 感觉作者 原文地址 http://o ...
- iOS开发中视图控制器ViewControllers之间的数据传递
iOS开发中视图控制器ViewControllers之间的数据传递 这里我们用一个demo来说明ios是如何在视图控制器之间传递重要的参数的.本文先从手写UI来讨论,在下一篇文章中讨论在storybo ...
- NavigationController.viewControllers
NSMutableArray *viewControllersArray = [NSMutableArray new]; // 获取当前控制器数组 for (CardLoanBaseT ...
- 关于viewControllers之间的传值方式
AViewController----Push----BViewController 1.属性 AViewController---pop----BViewController 1.代理 2.通知 ...
- iOS ViewControllers 瘦身
https://objccn.io/issue-1-1/ https://juejin.im/user/57ddfba4128fe10064cbb93a 把 Data Source 和其他 Proto ...
- iOS总结_UI层自我复习总结
UI层复习笔记 在main文件中,UIApplicationMain函数一共做了三件事 根据第三个参数创建了一个应用程序对象 默认写nil,即创建的是UIApplication类型的对象,此对象看成是 ...
- UIViewController生命周期-完整版
一.UIViewController 的生命周期 下面带 (NSObject)的方法是NSObject提供的方法.其他的都是UIViewController 提供的方法. load (NSObje ...
- 拦截UIViewController的popViewController事件
实现拦截UIViewController的pop操作有两种方式: 自定义实现返回按钮,即设置UIBarButtonItem来实现自定义的返回操作. 创建UINavigatonController的Ca ...
随机推荐
- Docker部署 Mysql .Net6等容器
Centos8安装Docker 1.更新一下yum [root@VM-24-9-centos ~]# yum -y update 2.安装containerd.io # centos8默认使用podm ...
- 细说ThreadLocal(一)
前言 java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.如下图所示: 其中堆是占虚拟机中内存最大的,堆被所有线程所共享,其最主要的便是存放实例对象.也因为堆内存是共 ...
- 分析师机构发布中国低代码平台现状分析报告,华为云AppCube为数字化转型加码
摘要:Forrester指出,中国企业数字化转型过程中,有58%的决策者正在采用低代码工具进行软件构建,另有16%的决策者计划采用低代码. 华为消息,知名研究与分析机构Forrester Resear ...
- [cf1168E]Xor Permutations
(与题目中下标不同,这里令下标为$[0,2^{k})$来方便运算) 根据异或的性质,显然有解的必要条件是$\bigoplus_{i=0}^{2^{k}-1}a_{i}=0$ 在此基础上,我们考虑构造- ...
- 如何用webgl(three.js)搭建处理3D园区、3D楼层、3D机房管线问题(机房升级版)-第九课(一)
写在前面的话: 说点啥好呢?就讲讲前两天的小故事吧,让我确实好好反省了一下. 前两天跟朋友一次技术对话,对方问了一下Geometry与BufferGeometry的具体不同,我一下子脑袋短路,没点到重 ...
- Java-ASM框架学习-修改类的字节码
Tips: ASM使用访问者模式,学会访问者模式再看ASM更加清晰 ClassReader 用于读取字节码,父类是Object 主要作用: 分析字节码里各部分内容,如版本.字段等等 配合其他Visit ...
- DPC++中的现代C++语言特性
Ⅰ DPC++简介 DPC++是Data Parallel C++(数据并行C++)的首字母缩写,它是Intel为了将SYCL引入LLVM和oneAPI所开发的开源项目.SYCL是为了提高各种加速设备 ...
- 使用Python定时清理运行超时的pdflatex僵尸进程
问题 在我们之前的<基于texlive定制chemfig化学式转换Python服务镜像>定制的pdflatex在线转换的镜像已经运行在生产环境了,但是最近总有人反馈服务跑着跑着就慢了,本来 ...
- 『学了就忘』Linux文件系统管理 — 60、Linux中配置自动挂载
目录 1.自动挂载 2.如何查询系统下每个分区的UUID 3.配置自动挂载 4./etc/fstab文件修复 上一篇文章我们说明了手动分区讲解,对一块新硬盘进行了手动分区和挂载. 但是我们发现重启系统 ...
- CF1542E2 Abnormal Permutation Pairs (hard version)
CF1542E2 Abnormal Permutation Pairs (hard version) good tea. 对于两个排列 \(p,q\),如果 \(p\) 的字典序小于 \(q\),则要 ...