实现控制器跳转的几种方式
1.可以通过UINavigationController来实现,它是一种类似“栈”的数据结构,控制器通过入栈和出栈实现跳转。
2.也可以通过UITabBarController来实现,它类似微信主界面下方的几个按钮,它可以实现几个界面之间的切换,把控制器实例化并添加即可。
3.或者直接在当前控制器下调用下一个控制器,实现跳转。
这次我们重点研究UINavigationController的使用。
UINavigationController的学习和使用
几个步骤:
- 实例化UINavigationController
- 把它作为当前window的根视图控制器。
- 实例化一个控制器作为UINavigationController的根控制器,也就是栈底控制器。
- 除此之外还需要用window的初始化操作。
创建它的代码:
这里的操作都是在delegate的回调中完成的,下一步就会进入第一个控制器的界面了。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ViewController *viewController=[[ViewController alloc]init];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:viewController];
self.window.rootViewController = nav;
self.window.backgroundColor = [UIColor clearColor];
[self.window makeKeyAndVisible];
return YES;
}
跳转到下一个控制器:
[self.navigationController pushViewController:viewController2 animated:YES];
弹出当前控制器,回到上一个控制器
[self.navigationController popViewControllerAnimated:YES];
在这个过程中会发生什么呢?
压栈后控制器初始化,显示控制器内容。弹栈后控制器销毁。
- (void)dealloc
{
NSLog(@"---------DEALLOC2--------");
}
这里实现了dealloc回调方法,可以看到在返回的时候,打印了 ---------DEALLOC2--------
整理控制器跳转的逻辑
因为第一个控制器在成为UINavigationController的根控制器后完成加载,然后在其中有一个按钮,点击按钮,触发监听方法,调用:
- (IBAction)Action:(id)sender
{
NSLog(@"添加控制器2");
ViewController2 *viewController2 = [[ViewController2 alloc] init];
[self.navigationController pushViewController:viewController2 animated:YES];
}
进入了控制器2,然后在控制器2中点击按钮,回到控制器1。
- (IBAction)ActionBack:(id)sender
{
NSLog(@"弹出当前控制器2");
self.block(self.textView.text);
[self.navigationController popViewControllerAnimated:YES];
}
其中的self.navigationController管理了整个控制器的pop和push逻辑。
UIView的生命周期
这个过程中view做了些什么事情呢?
在我的demo中实现了实现了view的回调方法,写了相应输出语句,先展示这些代码,我们结合现象分析view的生命周期。
直接给出了view生命周期的全部方法了。
#import "View2.h"
@interface View2()
{
NSInteger count;
}
@end
@implementation View2
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self){
self.backgroundColor = [UIColor yellowColor];
NSLog(@"<-- 1 %s , count = %@-->", __func__, @(count++));
}
return self;
}
- (void)willMoveToSuperview:(nullable UIView *)newSuperview
{
NSLog(@"<-- 2 %s , count = %@-->", __func__, @(count++));
}
- (void)didMoveToSuperview
{
NSLog(@"<-- 3 %s , count = %@-->", __func__, @(count++));
}
- (void)willMoveToWindow:(nullable UIWindow *)newWindow
{
NSLog(@"<-- 4/7 %s , count = %@-->", __func__, @(count++));
}
- (void)didMoveToWindow
{
NSLog(@"<-- 5/8 %s , count = %@-->", __func__, @(count++));
}
- (void)layoutSubviews
{
NSLog(@"<-- 6 %s , count = %@-->", __func__, @(count++));
}
- (void)removeFromSuperview
{
NSLog(@"<-- 9 %s , count = %@-->", __func__, @(count++));
}
- (void)dealloc
{
NSLog(@"<-- 10 %s , count = %@-->", __func__, @(count++));
}
@end
其中的__func__用于打印方法,count用于计数。
进入控制器2,加载view:
2019-03-30 17:16:09.220040+0800 test[1829:756806] 添加控制器2
2019-03-30 17:16:09.233373+0800 test[1829:756806] <-- 1 -[View2 initWithFrame:] , count = 0–>
2019-03-30 17:16:09.233512+0800 test[1829:756806] <-- 2 -[View2 willMoveToSuperview:] , count = 1–>
2019-03-30 17:16:09.233694+0800 test[1829:756806] <-- 3 -[View2 didMoveToSuperview] , count = 2–>
2019-03-30 17:16:09.254045+0800 test[1829:756806] <-- 4/7 -[View2 willMoveToWindow:] , count = 3–>
2019-03-30 17:16:09.254446+0800 test[1829:756806] <-- 5/8 -[View2 didMoveToWindow] , count = 4–>
2019-03-30 17:16:09.278930+0800 test[1829:756806] <-- 6 -[View2 layoutSubviews] , count = 5–>
离开控制器2,销毁view
2019-03-30 17:16:12.018141+0800 test[1829:756806] 弹出当前控制器2
2019-03-30 17:16:12.532822+0800 test[1829:756806] <-- 4/7 -[View2 willMoveToWindow:] , count = 6–>
2019-03-30 17:16:12.533176+0800 test[1829:756806] <-- 5/8 -[View2 didMoveToWindow] , count = 7–>
2019-03-30 17:16:12.534022+0800 test[1829:756806] ---------DEALLOC2--------
2019-03-30 17:16:12.534372+0800 test[1829:756806] <-- 9 -[View2 removeFromSuperview] , count = 8–>
2019-03-30 17:16:12.540893+0800 test[1829:756806] <-- 10 -[View2 dealloc] , count = 9–>
简单分析一下上面的过程:
以上方法都是系统回调。先是初始化,然后移到父视图,再移到window,最后加载布局。移除的过程因为涉及到,控制器的切换,调用了一次MoveToWindow,然后把view2从父视图移除,销毁view2。
通过block实现控制器之间的信息交互
这里涉及到了控制器的入栈和出栈,那么控制器2被销毁后,如何在回到控制器1点同时把数据带回去呢?这里我们可以想到block,block是一个全局的代码块,只要持有它的引用,就可以对它进行调用,方法中的操作可以实现信息的交互。
我的思路:
在控制器的view中添加一个label,在控制器2的view中添加一个textView,进入控制器2后在文本区输入内容,然后在控制器2被销毁前,把文本区的数据通过block的调用传递给控制器1在控制器1的label中显示。这也就实现了利用block回调数据。
代码:
控制器1:
#import "ViewController.h"
#define STWeakSelf __weak typeof(self) weakSelf = self;
@interface ViewController ()
@property (nonatomic ,strong) UILabel *label;
@end
@implementation ViewController
{
}
- (void)viewDidLoad
{
[super viewDidLoad];
View1 *view1 = [[View1 alloc] initWithFrame:self.view.frame];
[self.view addSubview:view1];
UIButton *button = [[UIButton alloc] init];
button.bounds = CGRectMake(0, 0, 200, 100);
button.center = CGPointMake(CGRectGetWidth(self.view.frame)/2, CGRectGetHeight(self.view.frame)/2);
button.backgroundColor = [UIColor redColor];
[button setTitle:@"enter" forState:UIControlStateNormal];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
button.titleLabel.font = [UIFont systemFontOfSize:24];
[button addTarget:self action:@selector(Action:) forControlEvents:UIControlEventTouchDown];
[self.view addSubview:button];
UILabel *label = [[UILabel alloc] init];
label.bounds = CGRectMake(0, 0, 200, 50);
label.center = CGPointMake(CGRectGetWidth(self.view.frame)/2, CGRectGetHeight(self.view.frame)/5);
label.backgroundColor = [UIColor whiteColor];
label.textColor = [UIColor blackColor];
label.textAlignment = NSTextAlignmentCenter;
self.label = label;
[self.view addSubview:label];
}
- (IBAction)Action:(id)sender
{
NSLog(@"添加控制器2");
ViewController2 *viewController2 = [[ViewController2 alloc] init];
STWeakSelf
viewController2.block = ^(NSString *text) {
NSLog(@"text is:%@",text);
weakSelf.label.text = text;
};
[self.navigationController pushViewController:viewController2 animated:YES];
}
- (void)dealloc
{
NSLog(@"---------DEALLOC--------");
}
@end
控制器2:
#import "ViewController2.h"
#import "ViewController3.h"
@interface ViewController2 ()
@property (nonatomic,strong) UITextView *textView;
@end
@implementation ViewController2
- (void)viewDidLoad {
[super viewDidLoad];
View2 *view2 = [[View2 alloc] initWithFrame:self.view.frame];
[self.view addSubview:view2];
UIButton *buttonBack = [[UIButton alloc] init];
buttonBack.bounds = CGRectMake(0, 0, 200, 100);
buttonBack.center = CGPointMake(CGRectGetWidth(self.view.frame)/2, CGRectGetHeight(self.view.frame)/3);
buttonBack.backgroundColor = [UIColor redColor];
[buttonBack setTitle:@"back" forState:UIControlStateNormal];
[buttonBack setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
buttonBack.titleLabel.font = [UIFont systemFontOfSize:24];
[buttonBack addTarget:self action:@selector(ActionBack:) forControlEvents:UIControlEventTouchDown];
[self.view addSubview:buttonBack];
UIButton *buttonNext = [[UIButton alloc] init];
buttonNext.bounds = CGRectMake(0, 0, 200, 100);
buttonNext.center = CGPointMake(CGRectGetWidth(self.view.frame)/2, CGRectGetHeight(self.view.frame)*2/3);
buttonNext.backgroundColor = [UIColor redColor];
[buttonNext setTitle:@"next" forState:UIControlStateNormal];
[buttonNext setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
buttonNext.titleLabel.font = [UIFont systemFontOfSize:24];
[buttonNext addTarget:self action:@selector(ActionNext:) forControlEvents:UIControlEventTouchDown];
[self.view addSubview:buttonNext];
UITextView *textView = [[UITextView alloc] init];
textView.bounds = CGRectMake(0, 0, 200, 50);
textView.center = CGPointMake(CGRectGetWidth(self.view.frame)/2, CGRectGetHeight(self.view.frame)*1/5);
textView.backgroundColor = [UIColor whiteColor];
self.textView = textView;
[self.view addSubview:textView];
}
- (IBAction)ActionBack:(id)sender
{
NSLog(@"弹出当前控制器2");
self.block(self.textView.text);
[self.navigationController popViewControllerAnimated:YES];
}
- (IBAction)ActionNext:(id)sender
{
NSLog(@"push控制器3");
ViewController3 *viewController3 = [[ViewController3 alloc] init];
[self.navigationController pushViewController:viewController3 animated:YES];
}
- (void)dealloc
{
NSLog(@"---------DEALLOC2--------");
}
@end
这里还涉及到了第3个控制器,在控制器2上还有添加控制器3的按钮,原理是一样的。
防止循环引用
这里控制器1创建了控制器2,控制器持有block的引用,block中使用到了控制器1,形成了一个循环引用,这种情况比较隐蔽,但是任意发生死锁之类的严重事故。为了避免双方强引用对方,需要一方改为弱引用,这样就不会出现资源争夺的危险。于是可以使用如下语句。
#define STWeakSelf __weak typeof(self) weakSelf = self;
- (IBAction)Action:(id)sender
{
NSLog(@"添加控制器2");
ViewController2 *viewController2 = [[ViewController2 alloc] init];
STWeakSelf
viewController2.block = ^(NSString *text) {
NSLog(@"text is:%@",text);
weakSelf.label.text = text;
};
[self.navigationController pushViewController:viewController2 animated:YES];
}
这里截取了部分代码,我们可以发现这里把block中的self通过宏定义改为了weakSelf,这就避免了循环引用,在回调的block中我们经常这样做。
效果展示:
黄色界面是控制器2,输入123,点击back,回到蓝色界面的控制器1,显示123这个回调。