在iOS开发中,视图的切换是很频繁的,常用的视图切换有三种:
导航控制器,用来组织有层次关系的视图,在UINavigationController中子控制器以栈(先进后出)的形式存储,只有在栈顶的控制器能够显示在界面中,一旦一个子控制器出栈则会被销毁;它必须有指定一个根控制器rootViewController才能创建,而且这个根控制器不会像其他子控制器一样被销毁,刚创建时,rootViewController即是栈底也是栈顶控制器;点击下一页就是控制器进栈,点击返回就是出栈(销毁).
导航条:
导航条的设置是根据栈顶控制器的navigationItem属性设置,导航条子控件是系统决定位置.高度44;
基本使用:
[[UINavigationController alloc] initWithRootViewController: rootController]
之后把导航控制器设为UIWindow的root控制器.添加子控制器,子控制器表现两种存储形式:viewControllers 和 childViewController数组.添加方式:
跳转-进栈
[self.navigationController pushViewController:vc animated:YES];
AppDelegate.m中:
//创建窗口
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//1. 创建导航控制器的根控制器
ViewController *vc = [[ViewController alloc] init];
vc.view.backgroundColor = [UIColor redColor];
//2. 创建导航控制器
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
nav.view.backgroundColor = [UIColor blueColor];
//引申可以在这里设置全局导航条风格和颜色
[[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
[[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
//3.把导航控制器设为窗口根控制器
self.window.rootViewController = nav;
//把窗口设为主窗口并显示
[self.window makeKeyAndVisible];
在子控制器ViewController中
//对于当前子视图来说其父控制器就是器导航控制器,可以获取.
//self.navigationController == self.parentViewController;
//在子视图中,可以通过navigationItem用于访问和设置导航条信息. (正在显示的导航条由栈顶即当前显示的子控制器来设置.)
self.navigationItem.title = @"haha"; (可以用self.title 代替 是系统内部封装的快速设置标题方法.)
//例:设置导航条左侧按钮
//方式一: 新建一个 UIButton *button = [[UIBarButtonItem alloc] init];
//给button设置图片,title等属性.
//导航条上子控件的位置是由系统决定的, 但是尺寸是由我们自己决定的.可以设置bounds; 可以使用自适应尺寸方法省得计算.
[button sizeToFit];
//最后,根据这个button来自定义创建导航条按钮.
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];
//方式二: 直接调用创建方法 -- //点击触发调用addFriends方法.
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"Icon.png"] style:UIBarButtonItemStyleDone target:self action:@selector(addFriends)];
}
//创建并 跳转下一个子视图
-(void)addFriends{
//通过push导航到另外一个子视图
QQViewController *qqViewController=[[QQViewController alloc]init];
[self.navigationController pushViewController:qqViewController animated:YES];
}
//iOS 7 之后, 默认会把导航条上的按钮的图片渲染成蓝色,可用取消自动渲染
// image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
下一级子控制器中,如果要返回.
a. 返回上一个(栈顶出栈)popViewControllerAnimated: ;
b. 返回根控制器(出栈至栈底)popToRootViewControllerAnimated: ;
c. 返回指定控制器(根据存储形式下标).popToViewController: .
UITabBarController是苹果专门为了利用页签切换视图而设计的,包含一个UITabBar控件,用户通过点击tabBar进行视图切换.为了尽可能减少视图之间的耦合,所有UITabBarController的子视图的相关标题、图标等信息均由子视图自己控制,UITabBarController仅仅作为一个容器存在。
结构:
和导航控制器类似:它的view用来存放UITabbar和子控制器view两部分;
不同点:
模态窗口只是视图控制器的显示的一种方式.不依赖与控制器容器;通常用于显示较独立的内容,在模态窗口显示的时,其他视图的内容无法进行操作.
使用起来比较容易,一般的视图控制器只要调用- (void)presentViewController:(UIViewController *)viewController animated: (BOOL)flag completion:(void (^)(void))completion
方法, 那么参数中的viewController就会以模态窗口的形式展现; 而此视图控制器再调用—(void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion
就会关闭模态窗口,回到原视图.
注意:modal出谁,谁才可以使用dismiss;
一般为了操作方便,会给模态窗口设置导航条,两种方式:
第一种:手动创建
//创建一个导航栏
UINavigationBar *navigationBar=[[UINavigationBar alloc]initWithFrame:CGRectMake(0, 0, 320, 44+20)];
//navigationBar.tintColor=[UIColor whiteColor];
[self.view addSubview:navigationBar];
//创建导航控件内容
UINavigationItem *navigationItem=[[UINavigationItem alloc]initWithTitle:@”Web Chat”];
//左侧添加登录按钮
_loginButton=[[UIBarButtonItem alloc]initWithTitle:@”登录” style:UIBarButtonItemStyleDone target:self action:@selector(login)];
navigationItem.leftBarButtonItem=_loginButton;
//添加内容到导航栏
[navigationBar pushNavigationItem:navigationItem animated:NO];
第二种: 把控制器包装成导航控制器.这是给控制器添加导航条的最快方法.
导航控制器和UITabBarController结合.一般由UITabBarButton做父控件:
原因:
界面跳转:
执行[self performSegueWithIdentifier:@”id” sender:nil];时
源控制器把数据->目标控制器设置的属性接收.代码示例:
在sourceViewController中;
// 判断账号密码
if ([self.accountField.text isEqualToString: @"hm"] && [self.pwdField.text isEqualToString: @"123"]) { // 账号密码都正确
// 跳转界面
[self performSegueWithIdentifier:@"login2Contact" sender:nil];
//跳转前会执行prepareForSegue方法 并传入sugue 和sender :可通过传入的segue获取来源和目标控制器; sender是之前performSegueWithIdentifier传入的sender;
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// 获取目标控制器.
UIViewController *vc = segue.destinationViewController;
// 顺传: 上一个控制器(来源控制器)把数据传递给下一个控制器(目标控制器)
vc.navigationItem.title = self.accountField.text;
}//可以传递模型数据.在目标控制器中设置属性接收即可.
原理是:来源控制器把自己->目标控制器,再在目标中设置来源控制器的属性. 但是:这种耦合性太高,所以引入代理模式解耦;
代理=>有限制的对象间关联关系,通过把两个关联对象用协议束缚起来,达到解耦目的.
iOS主要使用代理来逆传值,代码如下:
目标控制器中:
// .h中 定义协议
@protocol HMAddViewControllerDelegate
// 声明方法.
- (void)addViewControllerWith:(HMAddViewController *)addVc didClickButton:(HMContact *)contact; //contact是模型数据
// 新增delegate
@property (nonatomic, weak) id delegate;
//.m中
// 通知代理: 联系人控制器
if ([self.delegate respondsToSelector:@selector(addViewControllerWith:didClickButton:)]) {
[self.delegate addViewControllerWith:self didClickButton:contact];
}
来源控制器中.
//1. 在prepareForSegue方法中获取到目标控制器 . 让自己成为其代理
// 获取目标控制器(添加控制器)
HMAddViewController *addVc = segue.destinationViewController;
// 传递联系人控制器: 给目标控制器的 contactVc 属性赋值
addVc.delegate = self;
//2. 实现代理方法
- (void)addViewControllerWith:(HMAddViewController *)addVc didClickButton:(HMContact *)contact {
// 保存联系人模型
[self.contacts addObject:contact];
}
某些时候,系统的导航控制器和UITabBarController不能满足项目需求, 需要我们自定义跳转效果.
在主控制器上创建新控制器,并让新控制器View覆盖主View即可;
[self addChildViewController:[[HMOneViewController alloc] init]];
[self addChildViewController:[[HMTwoViewController alloc] init]];
@property (nonatomic, weak) UIViewController *showingChildVc;
-(void)switchVC:(int)index { //即将要显示的子控制器索引
//1. 移除当前正在显示的其他子控制器view
[self.showingChildVc.view removeFromSuperview];
//2. 添加index位置对应控制器的view,并设置frame,
UIViewController *newVc = self.childViewcontrollers[index];
newVc.view.frame = CGRectMake(0, 44, self.view.frame.size.width, self.view.frame.size.height - 44);
[self.view addSuperview:newVc.view];
//3. 记录要显示的子控制器
self.showingChildVc = newVc;
}
注意: 一定要建立需切换控制器的父子关系, 否则某些系统事件 子控制器无法接收并响应. 子控制器也无法获取父控制器的tabbar或导航控制器发送跳转消息.
- (void)switchVc:(int)index
{
UIViewController *newVc = self.childViewControllers[index];
// 如果index对应的子控制器正在显示,就直接返回
if (newVc == self.showingChildVc) return;
// 0.保存新旧控制器的索引
NSUInteger newIndex = index;
NSUInteger oldIndex = [self.childViewControllers indexOfObject:self.showingChildVc];
// 1.移除其它控制器的view
[self.showingChildVc.view removeFromSuperview];
// 2.添加index位置对应控制器的view
newVc.view.frame = self.contentView.bounds;
[self.contentView addSubview:newVc.view];
self.showingChildVc = newVc;
// 3.执行动画
if (oldIndex == NSNotFound) return;
CATransition *animation = [CATransition animation];
animation.type = @"cube";
if (newIndex > oldIndex) {
animation.subtype = kCATransitionFromRight;
} else {
animation.subtype = kCATransitionFromLeft;
}
animation.duration = 1.0;
[self.contentView.layer addAnimation:animation forKey:nil];
//3. 可使用UIView封装的动画 . 进行左右转场.
// 动画
/*
[UIView animateWithDuration:0.5 animations:^{
CGRect oldFrame = self.showingChildVc.view.frame;
oldFrame.origin.x = - self.view.frame.size.width;
self.showingChildVc.view.frame = oldFrame;
newVc.view.frame = self.contentView.bounds;
}completion:^(BOOL finished) {
[self.showingChildVc.view removeFromSuperview];
self.showingChildVc = newVc;
}];
*/
}