iOS开发>学无止境 - Cell 里的视图控制器

在每个 iOS 开发者的生涯中,总有一些时候想把一个视图控制器放到一个 tableView 的 cell 中。因为这是一个有用的工具去处理我在视图控制器中的各种复杂视图及繁琐操作,而且很容易想象的一种情况是你想要将一些视图堆在另一些视图上面。另一个常见的应用场景是将 collectionView 放在 cell 里。理想情况下里面的 collectionView 拥有它自己的控制器,这样外面的 tableView 控制器不会受到关联视图和每个 collection view cell 数据的影响。

因为 UIKit 有很多 hook(钩子函数)方法,用于组件之间与幕后相互通信,我们需要确保使用UIViewController 容器 API管理子视图控制器,如果不这样做可能会不知所以地失败或者表现不正常。

在这篇文章中我主要谈论 tableView 和它们的 cell,但是这些方法也适用于 collectionView。

为了简单起见,让视图控制器是 cell 中的唯一内容。相比管理单个视图控制器的根视图,尝试去管理一堆常规的视图反而会产生不必要的复杂。使用一个视图控制器并且每个 cell 中仅有一个视图控制器,布局(在 cell 层级)像下面这样简单

self.viewController.view.frame = self.contentView.bounds;

视图控制器能够内在处理自身的布局。我们也可以把高度的计算也放视图控制器里面。

这里有两种实现方法:可以每个 cell 持有一个视图控制器,也可以在控制器层管理这些视图控制器。

每个 cell 都持有视图控制器

如果我们在每个 cell 中放一个视图控制器,我们可以在这个 cell 中懒加载它。

- (SKContentViewController *)contentViewController {

if (!_contentViewController) {

SKViewController *contentViewController = [[SKContentViewController alloc] init];

self.contentViewController = contentViewController;

}

return _contentViewController;

}

记住我们不是将这个视图控制器的根视图作为一个子视图加入到我们 cell 的 contentView。当在 -cellForRowAtIndexPath: 方法中需要配置这个 cell 时,我们可以将我们的 model 传入到这个控制器,然后它会根据最新的内容配置自己。由于这些 cell 是复用的,你的控制器必须设计为在任何时候只要它的 model 改变就会完全地重置它自己。

UITableView 给我们了 cell 显示前后和移除前后的 hooks。我们想要在这个时候将 cell 的视图控制器加到我们的父表格视图控制器并且把 cell 视图控制器的根视图加到 cell 上.

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

[cell addViewControllerToParentViewController:self];

}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

[cell removeViewControllerFromParentViewController];

}

在 cell 的类中,实现这些方法来生成简单的视图控制器容器。

- (void)addViewControllerToParentViewController:(UIViewController *)parentViewController {

[parentViewController contentViewController];

[self.contentViewController didMoveToParentViewController:parentViewController];

[self.contentView addSubview:self.contentViewController.view];

}

- (void)removeViewControllerFromParentViewController {

[self.contentViewController.view removeFromSuperview];

[self.contentViewController willMoveToParentViewController:nil];

[self.contentViewController removeFromParentViewController];

}

在视图控制器作为子视图控制器加入之后,将 subview 加到视图中,确认 -viewWillAppear: 之类的方法能被正确的调用。tableView 的 willDisplayCell: 方法与显示方法(-viewWillAppear: 和 -viewDidAppear:)是对应的,并且 -didEndDisplayingCell: 方法与消失方法相对应,因此我们就可以在这些方法展示我们的容器了。

在父容器中持有视图控制器

每个 cell 有它自己的视图控制器能够正常工作,但是感觉有点怪异。在 Cocoa 的 MVC 模式中,模型和视图不应该知道它们所用的视图控制器,让一个 cell(实际上是一个 UIView)持有一个控制器违反了这个规则体系。为了解决这个问题,我们可以在表格视图控制器中,在父容器级别持有所有子视图控制器。

我们有两个方法来实现这个任务,(比较简单的方法是)我们可以为我们需要展示的表格中的每个 item 预生成一个视图控制器(和一个 view),或者在需要的时候生成视图控制器,然后循环使用它们,就像 UITableView 对 cell 的重用一样(这个比较困难)。首先从简单的方式开始,当 iPhone 刚出现的时候,设备受内存的限制不能为表格中的每行生成一个 view。现在我们的设备有更多的内存,所以如果当你只需要展示很少的行时可能不需要重用视图。

- (void)setupChildViewControllers {

self.contentViewControllers = [self.modelObjects arrayByTransformingObjectsUsingBlock:^id(SKModel *model) {

SKViewController *contentViewController = [[SKContentViewController alloc] initWithModel:model];

[self addChildContentViewController:contentViewController];

return contentViewController;

}];

}

- (void)addChildContentViewController:(UIViewController *)childController {

[self addChildViewController:childController];

[childController didMoveToParentViewController:self];

}

(上面我使用了 Objective-Shorthand 中的 -arrayByTransformingObjectsUsingBlock: 方法,也就是所谓的 -map:)

一旦你有了视图控制器,你可以在方法 -cellForRowAtIndexPath: 中把它们的视图放到 cell 里

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

UITableViewCell *cell = //make a cell

SKViewController *viewController = self.contentViewControllers[indexPath.row];

cell.hostedView = contentViewController.view;

return cell;

}

在 cell 里面,你可以拿到这个 hostedView,将它加到子视图中,并且在重用的时候清除它。

- (void)setHostedView:(UIView *)hostedView {

_hostedView = hostedView;

[self.contentView addSubview:hostedView];

}

- (void)prepareForReuse {

[super prepareForReuse];

[self.hostedView removeFromSuperview];

self.hostedView = nil;

}

这就是简单的方法你要做的所有事情。为了将视图控制器用于重用,需要一个 NSMutableSet 类型的 unusedViewControllers 和一个 NSMutableDictionary 类型的viewControllersByIndexPath.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

UITableViewCell *cell = //make a cell

SKViewController *viewController = [self recycledOrNewViewController];

viewController.model = [self.dataSource objectAtIndexPath:indexPath];

self.viewControllersByIndexPath[indexPath] = viewController;

cell.hostedView = contentViewController.view;

return cell;

}

- (UIViewController *)recycledOrNewViewController {

if (self.unusedViewControllers.count > 1) {

UIViewController *viewController = [self.unusedViewControllers anyObject];

[self.unusedViewControllers removeObject:viewController];

return viewController;

}

SKViewController *contentViewController = [[SKContentViewController alloc] init];

[self addChildViewController:contentViewController];

return contentViewController;

}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

UIViewController *viewController = self.viewControllersByIndexPath[indexPath];

[self.viewControllersByIndexPath removeObjectForKey:indexPath]

[self.unusedViewControllers addObject:viewController];

}

- (NSMutableSet *)unusedViewControllers {

if (!_unusedViewControllers) {

self.unusedViewControllers = [NSMutableSet set];

}

return _unusedViewControllers;

}

- (NSMutableDictionary *)viewControllersByIndexPath {

if (!_viewControllersByIndexPath) {

self.viewControllersByIndexPath = [NSMutableDictionary dictionary];

}

return _viewControllersByIndexPath;

}

有三件重要的事情,一,unusedViewControllers 里面装的是所有等待重用的视图控制器,二,viewControllersByIndexPaths 里面装的是所有正在用的视图控制器(我们必须持有它们,否则将会被销毁)。最后,cell 只与视图控制器的 hostedView 接触,符合我们之前的视图不能知道视图控制器原则。

这是我发现的适用于将 UIViewController 对象放入cell中的两个最好方法。如果我漏掉了任何技术我很乐意倾听。

英文出处:khanlou.com

本文由 伯乐在线 - Above 翻译,nathanw 校稿

译文链接:http://ios.jobbole.com/82123/

时间: 2024-10-14 13:02:52

iOS开发>学无止境 - Cell 里的视图控制器的相关文章

iOS开发指南 第7章 视图控制器与导航模式 学习

1 概述 分类:平铺导航模式 标签导航模式 树形导航模式 2 模态视图 必须要一个单独的模态视图控制器 呈现 代码方法:presentViewController:animated:completion: 故事板segue方式 关闭 dismissViewControllerAnimated:completion: 获取navigationBar:拖拽一个 Editor-Embed in-Navigation Controller 创建一个navigation controller interf

iOS开发>学无止境 - Cell异步图片加载优化,缓存机制详解

作者:勤奋的笨老头 网址:http://www.jianshu.com/p/02ab2b74c451 最近研究了一下UITbleView中异步加载网络图片的问题,iOS应用经常会看到这种界面.一个tableView上显示一些标题.详情等内容,在加上一张图片.这里说一下这种思路. 为了防止图片多次下载,我们需要对图片做缓存,缓存分为内存缓存于沙盒缓存,我们当然两种都要实现. 由于tableViewCell是有重用机制的,也就是说,内存中只有当前可见的cell数目的实例,滑动的时候,新显示cell会

IOS开发中UITableView(表视图)的性能优化及自定义Cell

IOS开发中UITableView(表视图)的滚动优化及自定义Cell IOS 开发中UITableView是非常常用的一个控件,我们平时在手机上看到的联系人列表,微信好友列表等都是通过UITableView实现的.UITableView这个控件中的列表的每一行是一个cell,当UITableView中cell数量特别大的时候,由于每次都需要alloc分配内存并初始化,会导致app运行不流畅,所以可以使用苹果提供的几个方法进行优化,我把这个过程记录下来供自己以后查阅. 当然,既然说到优化,那我们

iOS开发项目篇—03添加导航控制器

iOS开发项目篇—03添加导航控制器 一.简单说明 分析:分析微博应用,我们需要给每个子控制器都添加一个导航控制器(每个子控制器的导航不一样),所以需要新建一个导航控制器,然后把该导航控制器作为window的根控制器,添加的四个子控制器,分别添加在导航控制器上,也就是说整个项目采用当前主流的UI框架,一个UITabBarController管理着四个UINavigationController,而每个UINavigationController则分别管理着“首页”.“消息”.“发现”和“我”这四

iOS开发项目篇—02添加子控制器以及项目分层

iOS开发项目篇—02添加子控制器以及项目分层 一.添加子控制器 1.设置根控制器(自定义) 说明:分析新浪微博应用,观察其整体建构层次.而系统的控制器不能满足项目开发的需求,这里把项目中原有的控制器删除. 自己定义一个TabBarViewController类.让这个类作为window窗口的根控制器. YYAppDelegate.m文件代码: 1 #import "YYAppDelegate.h" 2 #import "YYTabBarViewController.h&qu

iOS开发>学无止境 - 浅谈MVVM的架构设计与团队协作

李刚按:本文是青玉伏案写的一篇文章.相信大家对MVC耳熟能详,MVVM可能听说的相对少一些,这一篇文章将会想你阐述MVVM设计,还有团队协作的经验分享.如果你也觉得不错,就分享一下吧! demo:https://github.com/lizelu/MVVM 今天写这篇文章是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇文章的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦~). 由 于本人项目经验有限,关于架构设

ios开发级联菜单(利用父子控制器--两个菜单封装为两个子控制器来实现)

一:1:级联菜单可以使用两个tableView来实现,也可以利用父子控制器,两个控制器来实现,根视图控制器作为两个控制器的父控制器,来管理两个子控制器.2:将左右菜单分别交给两个控制器去管理,对于一些复杂的业务逻辑,涉及大量回调操作,业务逻辑也要相对复杂,则不建议采取封装成view去处理,最好还是利用一个控制器去管理其内部复杂的业务逻辑,具体做法就是:利用父子控制器,将子控制器交由父控制器去管理,将子控制器的view添加到父控制器的view上.效果如图: 二:代码 1:根控制器代码:添加两个子控

[菜鸟成长记]iOS开发自学笔记06-导航控制器和segue的传递数据

导航控制器通常用来显示分层内容的向下导航界面,受限于设备屏幕大小,iPhone或者iPad需要通过更多的层次访问来显示足够丰富的内容,导航控制器一般会和表视图同时存在,但不是说必须一起绑定使用,这里将表视图嵌入到导航控制器中用于分层显示视图内容,以single view application为模板建立一个project,打开Main.storyboard,点击view controller再在工具栏上点击editor->embed in->navigation controller将视图控制

iOS 7:用代码解决视图控制器的View整体上移问题

如果你准备将你的老的 iOS 6 app 迁移到 iOS 7 上,那么你必须注意了.当你的老的 app 在 iOS 7 设备上运行时,所有ViewController 的视图都整体上移了,因为 iOS 7 把整个屏幕高度(包括状态栏和导航栏)都作为了视图控制器的有效高度.于是你的视图上移了,并和上层的状态栏交叠在一起. 你当然可以在 Xcode 中修改每个 View,将他们下移20个像素(状态栏高度)或者64个像素(状态栏+导航栏高度). 但是苹果显然已经考虑到这个问题,他们在 iOS 7 SD