开发中最为常见的功能,UITableViewCell中有个button需要跳转到下一个界面。
那么如何实现该功能,大致有三种办法:
PlanA:利用UIResponder响应链,寻找父层视图,直到找到UIViewController
概述:App使用响应者对象接收和处理事件,响应者对象是任何UIResponder的实例。UIResponder的子类包括UIView,UIViewController,UIApplication等。响应者接收到原始事件数据,必须处理事件或者转发到另一个响应者对象。当你的App接收到一个事件时,UIKit自动引导事件到最合适的响应者对象,也叫做第一响应者。
简单的说,我们一个界面会堆叠很多层的view,而点击事件一般会认为最上层的view是第一响应者。其他是二三四五六响应者。
我们可以通过nextResponder方法来获取这些响应者队列中的对象。
这种方法最好,低耦合,无循环引用的风险。(唯独对于不熟悉的人,阅读代码需要转个弯)
写法1:直接找下一个响应者对象
//只要是你的view在ViewController中,这条语句都会成功
//view在ViewController外,会陷入死循环,切记
-(UIViewController*)parentController{
UIResponder *responder = [self nextResponder];
while (responder) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)responder;
}
responder = [responder nextResponder];
}
//返回nil时候默认不会执行跳转
return nil;
}
写法2:先找当前view的superview,再找这个superview的下一个响应对象
//这种写法要求你的view在vc.view中
//因为这个写法会直接找self的superview,而[vc.view superview] = nil
- (UIViewController *)parentController{
for (UIView *next = [self superview]; next; next = next.superview) {
UIResponder *nextResponder = [next nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)nextResponder;
}
}
//返回nil时候默认不会执行跳转
return nil;
}
注:其实代码片段里的循环次数不会很多,不用担忧消耗内存。
PlanB:把view或者nav作为属性传进cell中
cell无法跳转下一界面的问题,主要是拿不到vc或者nav,所以我们把对象传进cell中,并控制好循环引用就好。
这种方法最符合普通逻辑,但在某些复杂情况下,可能造成内存泄漏。
cell的.h文件中声明
@interface TestTableViewCell : UITableViewCell
//注意,这里一定是weak修饰符
@property(nonatomic,weak)UINavigationController *nav;
//注意,这里一定是weak修饰符
@property(nonatomic,weak)UIViewController *rootView;
@end
然后在vc中的tableView:cellForRowAtIndexPath:方法中如此写
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TestTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"identifier" forIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:@"%d",self.count];
//只需要在cell中把nav属性声明为weak即可,这里不需要若引用
cell.nav = self.navigationController;
//只需要在cell中把rootView属性声明为weak即可,这里不需要若引用
cell.rootView = self;
//总结,外部是否使用弱应用对于cell无影响,cell中的属性设置weak才有效
return cell;
}
然后即可正常跳转
-(IBAction)goNextView:(id)sender{
TestTableViewController *testTableView = [[UIStoryboard storyboardWithName:@"TestStoryboard" bundle:nil] instantiateViewControllerWithIdentifier:@"TestTableView"];
//带nav执行跳转
[self.nav pushViewController:testTableView animated:YES];
//不带nav执行跳转
[self.rootView presentViewController:testTableView animated:YES completion:nil];
}
PlanC:利用bolck方式把点击事件传回ViewController
最早,可能大家用的都是这个方法,但实际上他的缺点有三:
1.block有造成内存泄漏的风险,需要使用__weak修饰符
2.如果这个cell中包含多个按钮,就意味着每个按钮都需要一个点击block。增加代码量
3.如果这个cell被多个tableView使用,每个tableView都需要把block代码块带上,这就产生了重复代码。
TestTableViewCell.h
@interface TestTableViewCell : UITableViewCell
//block一般用copy修饰符
@property(nonatomic,copy)void (^buttonClickBlock)(void);
@end
TestTableViewCell.m
-(IBAction)goNextView:(id)sender{
if (self.buttonClickBlock) {
self.buttonClickBlock();
}
}
然后在vc中的tableView:cellForRowAtIndexPath:方法中如此写
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TestTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"identifier" forIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:@"%d",self.count];
//切记,这里一定创建弱引用的weakSelf,再在cell的block中使用,否则必定会内存泄漏
__weak UIViewController *weakSelf = self;
[cell setButtonClickBlock:^{
TestTableViewController *testTableView = [[UIStoryboard storyboardWithName:@"TestStoryboard" bundle:nil] instantiateViewControllerWithIdentifier:@"TestTableView"];
[weakSelf.navigationController pushViewController:testTableView animated:YES];
}];
return cell;
}
附录:几种错误的写法
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TestTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"identifier" forIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:@"%d",self.count];
//经测试,如果cell中不对于nav属性声明weak,那么及时在此弱应用也是无效的
__weak UINavigationController *weakNav = self.navigationController;
cell.nav = weakNav;
//经测试,如果cell中不对于rootView属性声明weak,那么及时在此弱应用也是无效的
__weak UIViewController *weakSelf = self;
cell.rootView = weakSelf;
//总结,外部是否使用弱应用对于cell无影响,cell中的属性设置weak才有效
return cell;
}
网友评论