首页投稿(暂停使用,暂停投稿)iOS开发技术分享

UI部分-多控制器的切换与转场动画

2016-05-16  本文已影响1125人  ValienZh

多控制器切换.

1.概述.

在iOS开发中,视图的切换是很频繁的,常用的视图切换如下:

  1. UITabBarController的Tab切换
    • 以平行的方式管理视图,各个视图之间关系不大;每个加入的视图都会进行初始化,不论当前显不显示在界面上.所以相对比较占内存.
  2. UINavigationController 的 push和pop.
    • 以栈的方式管理视图,各个视图的切换就是压栈和出栈操作,出栈后的视图即被销毁.
  3. modal模态窗口.
    • 以模态窗口的形式管理视图presentation出现和dismissal关闭,当前视图关闭前其他视图上的内容无法操作。(遮盖)
  4. UICollectionViewController的布局转场,仅限于UICollectionViewController与UINavigationController结合的转场方式.
  5. 自定义控制器切换.

2. UINavigationController

2.1 简介

导航控制器,用来组织有层次关系的视图,在UINavigationController中子控制器以栈(先进后出)的形式存储,只有在栈顶的控制器能够显示在界面中,一旦一个子控制器出栈则会被销毁;它必须有指定一个根控制器rootViewController才能创建,而且这个根控制器不会像其他子控制器一样被销毁,刚创建时,rootViewController即是栈底也是栈顶控制器;

导航条:
导航条的设置是根据栈顶控制器的navigationItem属性设置,导航条子控件是系统决定位置.高度44;

基本步骤:

//1. 创建导航控制器
UINavigationController nav = [[UINavigationController alloc] initWithRootViewController:rootViewController];

//2. 添加子控制器,子控制器表现两种存储形式:viewControllers 和 childViewController数组.添加方式如下: 
    // nav.viewControllers = @[vc,vc2];
    //[nav addChildViewController:vc];
    //initWithRootViewController:vc;
//3.  跳转-进栈
[nav pushViewController:vc animated:YES]; //此方法默认封装了添加子控件方法.所以可以省略第二步

//4. 跳转-出栈
    a. 返回上一个(栈顶出栈)popViewControllerAnimated: ;
    b. 返回根控制器(出栈至栈底)popToRootViewControllerAnimated: ;
    c. 返回指定控制器(根据存储形式下标).popToViewController:  .
2.2 使用范例:

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用于访问和设置导航条信息. (屏幕正在显示的导航条信息就是栈顶子控制器的title)
self.navigationItem.title = @"haha";  //可以用self.title 代替 是系统内部封装的快速设置标题方法.

//例:设置导航条左侧按钮   
//方式一: 新建一个 
    UIButton *button = [[UIBarButtonItem alloc] init];
    //给button设置图片,title等属性.
    //导航条上子控件的位置是由系统决定的, 但是尺寸是由我们自己决定的.可以设置bounds;无特殊要求可以使用自适应方法.
    [button sizeToFit];    
    //最后,根据这个button来自定义创建导航条按钮.
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];

//方式二: 直接调用系统创建方法,根据style选择不同风格 
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"Icon.png"] style:UIBarButtonItemStyleDone target:self action:@selector(addFriends)];

}

//创建并转场下一个控制器
-(void)addFriends{
  UIViewController *vc=[[UIViewController alloc]init];
  self.hidesBottomBarWhenPushed = YES; // 隐藏底部tabBar.
  [self.navigationController pushViewController: vc animated:YES];
}    
//iOS 7 之后, 默认会把导航条上的按钮的图片渲染成蓝色,可取消自动渲染
//image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

下一级子控制器中,如果要返回.调用下面方法即可

a. 返回上一个(栈顶出栈)popViewControllerAnimated: ;
b. 返回根控制器(出栈至栈底)popToRootViewControllerAnimated: ;
c. 返回指定控制器(根据存储形式下标).popToViewController:  .
2.3 小结:

3. UITabBarController

3.1 简介

UITabBarController是苹果专门为了利用页签切换视图而设计的,包含一个UITabBar控件,用户通过点击tabBar进行视图切换.为了尽可能减少视图之间的耦合,所有UITabBarController的子视图的相关标题、图标等信息均由各自子视图自己控制,UITabBarController仅仅作为一个容器存在。

结构:
和导航控制器类似:它的自带view用来存放导航条UITabbar和子控制器view两部分;
不同点:

  1. 它的导航条UITabbar在下方,高度49;
  2. 不依赖RootViewController创建;
  3. UITabbar的子控件UITabBarButton样式跟栈顶控制器无关,只跟对应的子控制器有关.子控件数由子控制器数决定.位置是自动均分的,所以一般分四个,典型例子微信和QQ.
  4. 导航控制器出栈会销毁子控制器,UITabBarController不会.
3.2 一般步骤
  1. 新建初始化UITabbarController;并设为窗口的root控制器.
  2. 设置UITabBarButton样式:
    • 由对应子控制器的UITabBarItem设置.
    • 包含:title标题,image图标,selectedImage选中状态图标,badgeValue右上角内容通知个数;(iOS7之后系统自动渲染)
  3. 添加 子控制器方式.
    • [tb addChildViewController:c1];
    • tb.viewControllers=@[c1,c2,c3,c4];
  4. 跳转:点击UITabBarButton自动跳转.
3.3 小结

4. 模态窗口.

4.1 简介

模态窗口只是视图控制器的显示的一种方式.不依赖与控制器容器;通常用于显示较独立的内容,在模态窗口显示的时,其他视图的内容无法进行操作.

4.2 一般使用

使用起来比较容易,一般的视图控制器只要调用- (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];

第二种: 把控制器包装成导航控制器的root控制器.这是给控制器添加导航条的最快方法.


补充1:目前APP的主流框架
导航控制器和UITabBarController结合.一般由UITabBarButton做父控件:
原因:

补充2:使用Storyboard 工作时的切换
上面所说的控制器切换的触发方式主要有三种,包括:代码里调用相关方法, UINavigationBar和UITabBar自带的item点击操作, 最后一个就是Storyboard的Segue方式.如果你使用Storyboard进行开发,就需要了解一下.

  1. segue跳转原理.
    如果segue的type是push的,那么系统是取得sourceViewController所在的UINavigationViewController, 再调用push方法压入栈中完成跳转.
    如果segue的type是Modal的,那么系统会调用sourceViewController的 presentViewController方法,将destinationViewController展示出来.

  2. 界面跳转步骤:
    在 storyboard 里设置 segue有两种方式:Button to VC,这种在点击 Button 的时候触发转场;VC to VC,这种需要在代码中调用performSegueWithIdentifier:sender:
    prepareForSegue:sender:方法是在转场发生前修改转场参数的最后机会。这点对于 Modal 转场比较重要,因为在 storyboard 里 Modal 转场的 Segue 类型不支持选择 Custom 模式,使用 segue 方式触发时必须在prepareForSegue:sender:里修改模式。

  3. 控制器间值的传递:

//1 .来源控制器把数据->目标控制器.代码示例:
//sourceViewController中;
     [self performSegueWithIdentifier:@"login2Contact" sender:nil];        
//此方法跳转前会执行prepareForSegue方法
//并传入sugue 和sender :可通过传入的segue获取来源和目标控制器; 而sender是之前传入的nil;

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // 获取目标控制器.
    UIViewController *vc = segue.destinationViewController;
    // 顺传: 上一个控制器(来源控制器)把数据传递给下一个控制器(目标控制器) 很简单,直接传递即可.
    vc.navigationItem.title = self.accountField.text; 
}//可以传递模型数据.在目标控制器中设置属性接收即可.

// 2. 逆传值:
原理是:来源控制器把自己传给目标控制器,再在我们目前所在的目标vc中设置来源vc的属性. **但是:这种方式耦合性太高,所以引入delegate解耦;**
//代理:有限制的对象间关联关系,通过把两个关联对象用协议束缚起来,达到解耦目的.  

1. 来源VC 做 目标VC 的代理,我们在目标VC中定义协议,声明方法(把自己和数据作为参数)一般只传数据就够了,但是传自己是苹果delegate编程习惯. 来源VC来 遵守协议,实现方法.
2. 通过segue在目标VC中获取来源VC.调用此方法,传入数据.
3. **注意1:**调用前先做个判断(是否代理实现了方法,避免崩溃) respondsToSelect;
4.  **注意2:**如果一个控制器segue了多个控制器,那么用segue获取目标控制器需要判断一下-   isKindOfClass:  
5.  **注意3:**控制器顺传通常不能通过重写模型数据的set方法给子控件赋值,因为这个传值是在跳转之前传得,而此时控制器的view还没有加
载,也就意味着子控件还没创建.  所以把值传过去后,一般在viewdidload中设置子控件属性.
6. 其他逆传值方式:
    * block逆传值:捕获自动变量的匿名函数指针.
    * 通知机制
    * 通过文件存储
    * 通过AppDelegate定义全局变量(或者使用UIApplication、定义一个单例类等)
    
//代理来逆传值,代码如下:
//目标VC:
 
    // .h中 定义协议 
    @protocol HMAddViewControllerDelegate <NSObject>
    // 声明方法.
    - (void)addViewControllerWith:(HMAddViewController *)addVc didClickButton:(HMContact *)contact;    //contact是模型数据
    // 新增delegate
    @property (nonatomic, weak) id<HMAddViewControllerDelegate> delegate;
    
    //.m中
    // 通知代理: 联系人控制器
    if ([self.delegate respondsToSelector:@selector(addViewControllerWith:didClickButton:)]) {
        [self.delegate addViewControllerWith:self didClickButton:contact];
    }

来源VC中.
    
    //1. 在prepareForSegue方法中获取到目标控制器 .  让自己成为其代理
        // 获取目标控制器(添加控制器)
    HMAddViewController *addVc = segue.destinationViewController;
        // 传递联系人控制器: 给目标控制器的 contactVc 属性赋值
    addVc.delegate = self;
    
    //2. 实现代理方法
    - (void)addViewControllerWith:(HMAddViewController *)addVc didClickButton:(HMContact *)contact {
        // 保存联系人模型
    [self.contacts addObject:contact];
    
    }

5.自定义多控制器切换.

最后,很多时候,系统的导航控制器和UITabBarController并不能满足项目需求, 这时就需要我们自定义跳转效果.

1. 实现原理.

其实很简单:只需在主控制器上创建新子控制器,并让这个子控制器的View覆盖主View即可;

2. 细节与步骤.
[self addChildViewController:[[OneViewController alloc] init]];
[self addChildViewController:[[TwoViewController 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或导航控制器发送跳转消息.

自定义界面切换的方式和选择:

补充:使用系统类型的转场动画.

  1. 自定义动画代码因添加在切换控制器的方法中.
  2. 执行过渡动画的view要经历移除和添加事件.
  3. 动画执行调用CATransition类核心动画, 这个核心动画是添加在View的图层layer上的.示例代码:
- (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.执行动画,可根据subtype类型选择不同动画方式.
    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;
    }];
    */
}

补充: 完全自定义转场动画

上一篇下一篇

猜你喜欢

热点阅读