iOS8中屏幕旋转的问题,可以很酷的转场动画

虽然iOS8将渐渐地无需再被兼容,但是解决问题是必须的。

iOS屏幕旋转学习笔记iOS开发中使用屏幕旋转功能的相关方法

前言

    NS_CLASS_AVAILABLE_IOS(2_0) @interface UIViewController : UIResponder <NSCoding, UIAppearanceContainer, UITraitEnvironment, UIContentContainer, UIFocusEnvironment>
    @available(iOS 2.0, *)       public class UIViewController : UIResponder, NSCoding, UIAppearanceContainer, UITraitEnvironment, UIContentContainer, UIFocusEnvironment
  • 视图控制器负责页面的创建、事件处理等。

  • 每一个视图控制器(UIViewController)内部都有个默认的 UIView 属性,控制器中管理的其他所有控件都是这个 view 的子控件(直接或者间接)。

星彩网app下载 1

最近在调试程序时,遇到以下问题:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]'很奇怪,只有iOS8的设备报了异常,说我尝试从空数组插入空数据。全局断点定位在以下方法。

了解屏幕旋转首先需要区分两种 orientation星彩网app下载,1.1、device orientation设备的物理方向1.2、interface orientation界面显示的方向

1、ViewController 的创建

  • Objective-C

        // 实例化视图控制器
        UiViewController *viewController = [[UiViewController alloc] init];
    
        // 设置 window 的根视图控制器
        self.window.rootViewController = viewController;
    
  • Swift

        // 实例化视图控制器
        let viewController:UiViewController = UiViewController()
    
        // 设置 window 的根视图控制器
        self.window?.rootViewController = viewController
    

在iOS开发中,界面间的跳转其实也就是控制器的跳转,跳转有很多种,最常用的有push,modal.

[self presentViewController:controller animated:YES completion:nil];

iOS提供了在设备旋转时,界面显示发生相应适配的能力,以达到方便用户使用并提供最佳显示效果的目的。开发者需要指定应用支持的显示方向,并对界面显示做出对应的适配。由于界面适配的工作量相当大,目前国内的应用大都只支持默认的竖屏方向。

2、ViewController 的设置

  • Objective-C

        // 设置 viewController 的背景颜色
        self.view.backgroundColor = [UIColor redColor];
    
        // 向 viewController 上添加视图
        [self.view addSubview:button];
    
        // 向 viewController 上添加视图控制器
        [self addChildViewController: viewController2];
        [self.view addSubview:viewController2.view];
    
  • Swift

        // 设置 viewController 的背景颜色
        self.view.backgroundColor = UIColor.redColor()
    
        // 向 viewController 上添加视图
        self.view.addSubview(button)
    
        // 向 viewController 上添加视图控制器    
        self.addChildViewController(viewController2)
        self.view.addSubview(viewController2.view)
    
  • modal:任何控制器都能通过Modal的形式展⽰出来.效果:新控制器从屏幕的最底部往上钻,直到盖住之前的控制器为⽌.系统也会带有一个动画.

于是想方设法在附近找bug。一顿操作后发现,将以上代码中的controller是一个横屏的ViewController,而代码中的self中是一个竖屏的ViewController。在modal跳转的时候屏幕会发生旋转,接着就异常了。

加速计是整个IOS屏幕旋转的基础。依赖加速计,设备才可以判断出当前的设备方向。当加速计检测到方向变化的时候,会发出UIDeviceOrientationDidChangeNotification 通知。屏幕旋转的流程如下:1>、加速计来识别设备的旋转方向。发送 UIDeviceOrientationDidChangeNotification 设备旋转的通知。2>、app 接收到旋转事件。2>、app 通过AppDelegate通知当前程序的KeyWindow。3>、Window 会知会它的 rootViewController,判断该view controller所支持的旋转方向,完成旋转。4>、如果存在 modal 的view controller的话,系统则会根据 modal 的view controller,来判断是否要进行旋转。

3、ViewController 的界面跳转

  • Objective-C

        // 跳转到 指定 页面
        [self presentViewController:viewController1 animated:YES completion:nil];
    
        // 返回到 上一个 页面
        [self dismissViewControllerAnimated:YES completion:nil];
    
  • Swift

        // 跳转到 指定 页面
        self.presentViewController(viewController1, animated: true, completion: nil)
    
        // 返回到 上一个 页面
        self.dismissViewControllerAnimated(true, completion: nil)
    

为了证明我的观点,我尝试着把以下代码屏蔽,发现的确异常消失了。

UIDevice 对象可以选择性的接收UIDeviceOrientationDidChangeNotification 通知。

4、ViewController 的生命周期

星彩网app下载 2

  • 相关方法执行顺序:

    init -> loadView -> viewDidLoad -> viewWillAppear -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidAppear -> viewWillDisappear -> viewDidDisappear -> viewWillUnload -> viewDidUnload —> dealloc

    分配内存 -> 加载视图 -> 视图已经加载 -> 视图将要出现 -> 将要布局子视图 -> 已经布局子视图 -> 视图已经出现 -> 视图将要消失 -> 视图已经消失 -> 将要销毁视图 (iOS 6 已废弃)-> 已经销毁视图(iOS 6 已废弃) -> 释放内存

  • viewController 对 view 加载过程:

    • 1、先判断子类是否重写了 loadView,如果有直接调用。之后调 viewDidLoad 完成 View 的加载。
    • 2、如果是外部通过调用 initWithNibName:bundle 指定 nib 文件名的话,ViewController 加载此 nib 来创建 View。
    • 3、如果 initWithNibName:bundle 的 name 参数为 nil,则 ViewController 会通过以下两个步骤找到与其关联的 nib。
      • A、如果类名包含 Controller,例如 ViewController 的类名是 MyViewController,则查找是否存在 MyView.nib;
      • B、找跟 ViewController 类名一样的文件,例如 MyViewController,则查找是否存在 MyViewController.nib。
    • 4、如果子类没有重写的 loadView,则 ViewController 会从 StroyBoard 中找或者调用其默认的 loadView,默认的 loadView 返回一个空白的 UIView 对象。
  • loadView

        - (void)loadView;
        public func loadView()
    
    • 加载视图层次结构,用纯代码开发应用程序时使用,功能和 Storyboard & XIB 是等价的,如果重写了 loadView,Storyboard & XIB 都无效。如果控制器的 view 为 nil,会自动调用 loadView 方法,创建视图。
  • awakeFromNib

        - (void)awakeFromNib;
        public func awakeFromNib()
    
    • 从 Stroyboard 加载,还没有被设置 frame & bounds。这个方法用的时候,outlet 还没有连接起来,是 ViewController 刚从 storyboard 建的时候没有完全建好,不过可能有一些事情要在这个方法里面完。
  • viewDidLoad

        - (void)viewDidLoad;
        public func viewDidLoad()
    
    • 用这个的时候,ViewController 已经完全好了,outlet 也已经连接好了。但是还没有在屏幕上显示出来。这个方法里面可以放很多设置的代码。这个方法执行的时候,view 的 bounds 还没有。先 load,再 appear。

    • 视图加载完成后执行,可以做一些数据初始化的工作,如果用纯代码开发,不要在此方法中设置界面 UI。如果是 Storyboard 开发,可以动态添加一些控件,以及加载数据。

  • viewWillAppear

        - (void)viewWillAppear:(BOOL)animated;
        public func viewWillAppear(animated: Bool)
    
    • 这个方法调用的时候,视图的 bounds 已经有了。

    • 视图只会 loaded 一次,但是会 appear 或者 disappear 很多次。不变的东西,放在 viewDidLoad 里面。和几何相关的,放在 viewWillAppear 里面。这点对项目的优化很重要。就好似顶层的 view,旋转 ipad 什么的都需要改变顶层的 view 的大小,当一个 ViewController 的生命周期到这里的时候,就可以在这里的最后时刻来调整 view 的排列或者几何特性。

    • 这里也设置做一些 lazy execution for performance。比如:需要按一个 button,出现一个 view 什么的。这里设置,开销很大。耗时很长的事情最好在 viewWillAppear 里另开一个线程运行,然后在 view 里面放一个小小的 spinning wheel。

  • viewWillLayoutSubviews

       - (void)viewWillLayoutSubviews;
        public func viewWillLayoutSubviews()
    
    • 这个方法专门用来布局子控件,一般在这里设置子控件的 frame,当控件本身的尺寸发生改变的时候,系统会自动调用这个方法。

    • layoutSubviews 在以下情况下会被调用:

      • 1、init 初始化不会触发 layoutSubviews。
      • 2、addSubview 会触发 layoutSubviews。
      • 3、设置 view 的 Frame 会触发 layoutSubviews,当然前提是 frame 的值设置前后发生了变化。
      • 4、滚动一个 UIScrollView 会触发 layoutSubviews。
      • 5、旋转 Screen 会触发父 UIView 上的 layoutSubviews 事件。
      • 6、改变一个 UIView 大小的时候也会触发父 UIView 上的 layoutSubviews 事件。
  • viewWillDisappear

        - (void)viewWillDisappear:(BOOL)animated;
        public func viewWillDisappear(animated: Bool)
    
    • 这个方法在视图将要消失的时候调用。要消失的时候,如果要记得现在的运行情况,如可以记的 scroll 的 position。但是,不要在这个方法里面写太多的东西,否则 App 会崩溃的。另外开线程来处理任何 UI 的改变,或者如果是不怎么废资源的话就直接写入硬盘。
  • Objective-C

        // 纯代码加载视图
        - (void)loadView {
    
            [super loadView];
    
        }
    
        // 从 Nib 加载视图
        - (void)awakeFromNib {
    
            [super awakeFromNib];
    
        }
    
        // 视图已经加载
        - (void)viewDidLoad {
    
            [super viewDidLoad];
    
        }
    
        // 视图将要出现,在 viewDidLoad 执行完成之后执行
        - (void)viewWillAppear:(BOOL)animated {
    
            [super viewWillAppear:animated];
    
        }
    
        // 将要布局子视图
        - (void)viewWillLayoutSubviews {
    
            [super viewWillLayoutSubviews];
    
        }
    
        // 已经布局子视图
        - (void)viewDidLayoutSubviews {
    
            [super viewDidLayoutSubviews];
    
        }
    
        // 视图已经出现
        - (void)viewDidAppear:(BOOL)animated {
    
            [super viewDidAppear:animated];
    
        }
    
        // 视图将要消失
        - (void)viewWillDisappear:(BOOL)animated {
    
            [super viewWillDisappear:animated];
    
        }
    
        // 视图已经消失,不是销毁
        - (void)viewDidDisappear:(BOOL)animated {
    
            [super viewDidDisappear:animated];
    
        }
    
  • Swift

        // 纯代码加载视图
        override func loadView() {
    
            super.loadView()
    
        }
    
        // 从 Nib 加载视图
        override func awakeFromNib() {
    
            super.awakeFromNib()
    
        }
    
        // 视图已经加载
        override func viewDidLoad() {
    
            super.viewDidLoad()
    
        }
    
        // 视图将要出现,在 viewDidLoad 执行完成之后执行
        override func viewWillAppear(animated: Bool) {
    
            super.viewWillAppear(animated)
    
        }
    
        // 将要布局子视图
        override func viewWillLayoutSubviews() {
    
            super.viewWillLayoutSubviews()
    
        }
    
        // 已经布局子视图
        override func viewDidLayoutSubviews() {
    
            super.viewDidLayoutSubviews()
    
        }
    
        // 视图已经出现
        override func viewDidAppear(animated: Bool) {
    
            super.viewDidAppear(animated)
    
        }
    
        // 视图将要消失
        override func viewWillDisappear(animated: Bool) {
    
            super.viewWillDisappear(animated)
    
        }
    
        // 视图已经消失,不是销毁
        override func viewDidDisappear(animated: Bool) {
    
            super.viewDidDisappear(animated)
    
        }   
    
public func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion:  -> Void)?)public func dismissViewControllerAnimated(flag: Bool, completion:  -> Void)?)
- shouldAutorotate { return YES;}- (UIInterfaceOrientationMask) supportedInterfaceOrientations { return UIInterfaceOrientationMaskLandscape;}
// 是否已经开启了设备方向改变的通知@property(nonatomic,readonly,getter=isGeneratingDeviceOrientationNotifications)BOOL generatesDeviceOrientationNotifications__TVOS_PROHIBITED;// 开启接收接收 UIDeviceOrientationDidChangeNotification 通知- beginGeneratingDeviceOrientationNotifications__TVOS_PROHIBITED; // nestable// 结束接收接收 UIDeviceOrientationDidChangeNotification 通知- endGeneratingDeviceOrientationNotifications__TVOS_PROHIBITED;

5、ViewController 更新布局约束

  • 通过代码为 xib 或 storyboard 中 view 增加约束时,尽量避免在 viewDidLoad 中执行,最好放在 updateViewConstraints(UIViewController 中) 或者 updateConstraints(UIView 中)中,记得调用 [super updateViewConstraints] 或者 [super updateConstraints];

  • 如果你真的写在 viewDidLoad 里了,那么可能会遇到这种崩溃错误 Terminating app due to uncaught exception "NSInternalInconsistencyException"。

  • 代码设置约束时,需设置该 view 的 translatesAutoresizingMaskIntoConstraints 属性已设置为 NO。

  • Objective-C

        // 更新视图控制器中视图的布局约束
        - (void)updateViewConstraints {
    
            // 在这里为你的 view 添加约束
    
            [super updateViewConstraints];
        }
    
        // 更新视图布局约束
        - (void)updateConstraints {
    
            // 在这里为你的 view 添加约束
    
            [super updateConstraints];
        }
    
  • Swift

        // 更新视图控制器中视图的布局约束
        override func updateViewConstraints() {
    
            // 在这里为你的 view 添加约束
    
            super.updateViewConstraints()
        }
    
        // 更新视图布局约束
        override func updateConstraints() {
    
            // 在这里为你的 view 添加约束
    
            super. updateConstraints()
        }
    
  • push: 在push中,控制器的管理其实交给了UINavigationController,所以在push控制器的时候必须拿到对应的导航控制器. 效果:从右往左出现,系统会带有一个默认动画.

如果有人也遇到了这个问题,并读到了这里。然后各种搜索引擎“iOS8 屏幕旋转bug”。好巧,我们的方向都完全错了!

在 app 代理里面结束接收 设备旋转的通知事件, 后续的屏幕旋转都会失效

6、ViewController 的内存警告

  • iPhone 下每个 app 可用的内存是被限制的,如果一个 App 使用的内存超过 20M,则系统会向该 App 发送 Memory Warning 消息。苹果公司系统工程师建议,应用程序所占内存不应该超过 20MB,开发人员圈内流传着一个粗略的经验法则:当应用程序占用了大约 20MB 内存时,iPhone 开始发出内存警告。收到消息后 App 必须尽可能多的释放一些不必要的内存,当应用程序所占内存大约为 30MB 时,iPhone OS 会关闭应用程序。收到此消息后,App 必须正确处理,否则可能出错或者出现内存泄露。App 收到 Memory Warning 后会调用:UIApplication::didReceiveMemoryWarning -> UIApplicationDelegate::applicationDidReceiveMemoryWarning,然后调用当前所有的 viewController 进行处理。因此处理的主要工作是在 viewController。

  • 内存警告处理思路

    • 通常一个应用程序会包含多个 viewController,当从 view 跳转到另一个 view 时,之前的 view 只是不可见状态,并不会立即被清理掉,而是保存在内存中,以便下一次的快速显现。但是如果应用程序接收到系统发出的 low-memory warning,我们就不得不把当前不可见状态下的 views 清理掉,腾出更多的可使用内存,当前可见的 viewController 也要合理释放掉一些缓存数据,图片资源和一些不是正在使用的资源,以避免应用程序崩溃。具体的实施根据系统版本不同而略有差异。
  • iOS 5 的处理

    • 在 iOS 6 之前,如果应用程序接收到了 low-memory 警告,当前不可见的 viewController 会接收到 viewDidUnload 消息(也可以理解为自动调用 viewDidUnload 方法),所以我们需要在 viewDidUnload 方法中释放掉所有 outlets ,以及可再次创建的资源。当前可见的 viewController 通过 didReceiveMemoryWarning 合理释放资源。

    • 有这样一个 viewController。

          @interface MyViewController : UIViewController { 
              NSArray *dataArray; 
          } 
      
          @property (nonatomic, strong) IBOutlet UITableView *tableView; 
      
          @end
      
    • 对应的处理为。

          - (void)didReceiveMemoryWarning {
      
              // Releases the view if it doesn't have a superview.
              [super didReceiveMemoryWarning];
      
              // Relinquish ownership any cached data, images, etc that aren't in use.
          }
      
          - (void)viewDidUnload {
      
              // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
              // For example: self.myOutlet = nil;
              self.tableView = nil;
              dataArray = nil;
      
              [super viewDidUnload];
          }
      
  • iOS 6 的处理

    • iOS 6 废弃了 viewDidUnload 方法,这就意味着一切需要我们自己在 didReceiveMemoryWarning 中操作。

    • 1、将 outlets 置为 weak。当 view dealloc 时,没有人握着任何一个指向 subviews 的强引用,那么 subviews 实例变量将会自动置空。

          @property (nonatomic, weak) IBOutlet UITableView *tableView;
      
    • 2、在 didReceiveMemoryWarning 中将缓存数据置空。不要忘记一点,每当 tableview reload 的时候,需要判断一下 dataArray ,若为空则重新创建。

          - (void)didReceiveMemoryWarning { 
      
              [super didReceiveMemoryWarning]; 
              // Dispose of any resources that can be recreated.
      
              dataArray = nil; 
          }
      
  • 兼容 iOS 5 与 iOS 6

    • 倘若希望程序兼容 iOS 5 与 iOS 6 怎么办呢?这里有一个小技巧,我们需要对 didReceiveMemoryWarning 做一些手脚。

          - (void)didReceiveMemoryWarning {
      
              [super didReceiveMemoryWarning];
      
              if (self.isViewLoaded && self.view.window == nil) {
                  self.view = nil;
              }
      
              dataArray = nil;
          }
      
      • 判断一下 view 是否是 window 的一部分,如果不是,那么可以放心的将 self.view 置为空,以换取更多可用内存。这样会是什么现象呢?假如,从 viewController A 跳转到 viewController B ,然后模拟 low-memory 警告,此时,viewController A 将会执行 self.view = nil ; 当我们从 B 退回 A 时, A 会重新调用一次 viewDidLoad ,此时数据全部重新创建,

以上所有代码都没问题。也不是iOS8的系统bug。

- application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 结束接收接收 UIDeviceOrientationDidChangeNotification 通知 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; return YES;}
public func pushViewController(viewController: UIViewController, animated: Bool) public func popViewControllerAnimated(animated: Bool) -> UIViewController?

问题其实是出在self或者self.parentViewController或者self.parentViewController.parentViewController……上。他们中某一个ViewController被多次modal。

被多次modal的方式可能是多次presentViewController,多次performSegueWithIdentifier,还有在storyboard上关联了按钮的跳转事件,又在点击事件里又写了一次跳转等等。

其实问题本来很简单,但是由于1、iOS8以上的系统兼容了这个错误。并不会异常。2、异常定位位置奇葩。导致很容易误导开发者。

之前也有遇到其他的异常相信以后也不会避免,在此提醒一下自己:1、不要轻易怀疑系统bug,99%的程序问题都是因为有问题的程序员。遇到问题应该首先定位自己的问题,不要先入为主认为是别人的问题,包括系统bug,后台bug,底层bug等。2、调试方法。传统的打断点、写日志等方法虽然方便,但是遇到奇葩的问题往往事倍功半。屏蔽代码,写demo,更加有效但是相对麻烦。

3.1、全局控制Info.plist文件中,有一个Supported interface orientations,可以配置整个应用的屏幕方向,此处为 全局控制3.2、UIWindowiOS6的UIApplicationDelegate提供了下述方法,能够指定 UIWindow 中的界面的屏幕方向:

push和model都有系统提供转场动画效果,但有时系统提供的不一定能满足开发需求,这就需要去自定义,说实话自定义转场动画还是有些麻烦的.但原理其实很简单.

// 该方法默认值为 Info.plist 中配置的 Supported interface orientations 项的值。- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window NS_AVAILABLE_IOS; 

转场动画的原理:

iOS中通常只有一个 window,所以此处的控制也可以视为全局控制。3.3、controller 只有以下两种情况:当前 controller 是 window 的 rootViewController当前 controller 是 modal 模式的时(controller 为 modal 显示的控制器),orientations相关方法才会起作用,当前controller及其所有的childViewController都在此作用范围内。

当两个控制器发生push.pop或modal.dismiss的时候,系统会把原始的控制器放到负责转场的控制器容器中,也会把目标控制器放进去,但是目标控制器是不可见的,因此我们要做的就是把新的控制器显现出来,把老的控制器移除掉.很简单吧!

3.4、最终支持的屏幕方向一个界面最后支持的屏幕方向,是取 (全局控制 ∩ UIWindow 中的界面控制 ∩ 单个界面控制)的交集。如果全局控制支持所有屏幕方向,UIWindow 中的界面控制支持横屏,当个界面中只是支持横屏向右,那么最后界面只会以横屏向右显示,并且不支持旋转到其他的方向。

上面大概介绍了一下控制器的切换及原理,这篇文章我们打算说说自定义转场动画,一个push一个modal,虽然自定义modal转场动画用的比较多一点,但push也可以了解一下嘛!先来看下效果,然后准备上车:

如果最终的交集为空,在iOS6以后会抛出UIApplicationInvalidInterfaceOrientationException崩溃异常。

星彩网app下载 3自定义modal转场动画星彩网app下载 4自定义push转场动画星彩网app下载 5

// To make it more convenient for applications to adopt rotation, a view controller may implement the below methods. Your UIWindow's frame should use [UIScreen mainScreen].bounds as its frame.// 为了让控制器更加方便的旋转,视图控制器可以实现下面这些方法。你的 UIWindow 的 frame 将要使用 UIScreen mainScreen].bounds 作为他的 frame。@interface UIViewController (UIViewControllerRotation)// call this method when your return value from shouldAutorotateToInterfaceOrientation: changes// if the current interface orientation does not match the current device orientation, a rotation may occur provided all relevant view controllers now return YES from shouldAutorotateToInterfaceOrientation:// 试着去旋转到界面的方向// 使用场景是 interface orientation和device orientation 不一致,// 希望通过重新指定 interface orientation 的值,立即实现二者一致;// 如果这时只是更改了支持的 interface orientation 的值,没有调用attemptRotationToDeviceOrientation,那么下次 device orientation 变化的时候才会实现二者一致,关键点在于能不能立即实现。  attemptRotationToDeviceOrientation NS_AVAILABLE_IOS __TVOS_PROHIBITED;// Applications should use supportedInterfaceOrientations and/or shouldAutorotate.. // 应用将要使用界面支持的方向,或者将要自动旋转 (在 iOS6 以后被禁用, 要兼容 iOS 6 还是需要实现这个方法)- shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation NS_DEPRECATED_IOS __TVOS_PROHIBITED;// New Autorotation support. // 新的自动旋转的支持 (上面的方法被下面两个方法替代)// 将要自动旋转 (在 iOS6 开始启用)返回 yes 为支持旋转,no 为不支持旋转- shouldAutorotate NS_AVAILABLE_IOS __TVOS_PROHIBITED;// 支持的界面方向- (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS __TVOS_PROHIBITED;// Returns interface orientation masks.// 返回现在正在显示的用户界面方向- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS __TVOS_PROHIBITED;// The rotating header and footer views will slide out during the rotation and back in once it has completed.- (nullable UIView *)rotatingHeaderView NS_DEPRECATED_IOS(2_0,8_0, "Header views are animated along with the rest of the view hierarchy") __TVOS_PROHIBITED; // Must be in the view hierarchy. Default returns nil.- (nullable UIView *)rotatingFooterView NS_DEPRECATED_IOS(2_0,8_0, "Footer views are animated along with the rest of the view hierarchy") __TVOS_PROHIBITED; // Must be in the view hierarchy. Default returns nil.// 获取用户界面的方向 (方法在 iOS8 被禁用,方法是只读的)@property(nonatomic,readonly) UIInterfaceOrientation interfaceOrientation NS_DEPRECATED_IOS __TVOS_PROHIBITED;// Notifies when rotation begins, reaches halfway point and ends.// 当旋转开始,到达一半,结束的时候,将通过下列方法得到通知。// 将要旋转到用户界面- willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS(2_0,8_0, "Implement viewWillTransitionToSize:withTransitionCoordinator: instead") __TVOS_PROHIBITED;// 已经从某个用户界面开始旋转- didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation NS_DEPRECATED_IOS __TVOS_PROHIBITED;// 将要动画旋转到用户界面- willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS(3_0,8_0, "Implement viewWillTransitionToSize:withTransitionCoordinator: instead") __TVOS_PROHIBITED;// 界面切换到一半的控制- willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS __TVOS_PROHIBITED;- didAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation NS_DEPRECATED_IOS __TVOS_PROHIBITED; // The rotating header and footer views are offscreen.- willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS __TVOS_PROHIBITED; // A this point, our view orientation is set to the new orientation.@end

// Applications should use supportedInterfaceOrientations and/or shouldAutorotate.. // 应用将要使用界面支持的方向,或者将要自动旋转 (在 iOS6 以后被禁用, 要兼容 iOS 6 还是需要实现这个方法)- shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation NS_DEPRECATED_IOS __TVOS_PROHIBITED;

很简单的modal效果,首先有一个主控制器,主控制器底部有一个scrollView,对scrollView里面图片添加手势监听,并在监听方法里面modal一个背景图片一样的控制器,这样就可以开始自定义转场动画啦!

这一个接口被下面两个接口替代 (在 iOS6 以后)

  1. 新建一个继承 NSObject, UIViewControllerAnimatedTransitioning的PopAnimator文件,用于设置转场动画的代理方法,在里面添加UIViewControllerAnimatedTransitioning协议必须要实现的两个代理方法:
// 将要自动旋转 (在 iOS6 开始启用)返回 yes 为支持旋转,no 为不支持旋转- shouldAutorotate NS_AVAILABLE_IOS __TVOS_PROHIBITED;// 支持的界面方向 (在 iOS6 开始启用)- (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS __TVOS_PROHIBITED;

如果需要兼容 iOS6 之前的系统还是需要实现老的接口,要不 app 只能支持竖屏不能旋转

 // 设置转场动画持续时间 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0 } // 执行转场动画 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { }

当只需要锁定屏幕的旋转不需要设置屏幕的旋转方向

  1. 在ViewController中设置herbDetailsVc(每张图片对应的控制器)的 transitioningDelegate为自己,为刚刚新建的文件设置一个常量 let transition = PopAnimator(),新建一个extension遵守代理UIViewControllerTransitioningDelegate
// 是否支持屏幕旋转-shouldAutorotate { return NO;}

开启屏幕旋转并设置屏幕旋转支持的方向

extension ViewController: UIViewControllerTransitioningDelegate { func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return transition } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return nil }}
// 是否支持屏幕旋转 (返回 NO 后面俩方法不调用,后面只支持竖直方向)-shouldAutorotate { return NO;}// 支持屏幕旋转的方向- (UIInterfaceOrientationMask)supportedInterfaceOrientations { // 通过设置返回的枚举值来改变屏幕旋转支持的方向 //(iPad上的默认返回值是UIInterfaceOrientationMaskAll, // iPhone上的默认返回值是UIInterfaceOrientationMaskAllButUpsideDown) return UIInterfaceOrientationMaskAll;}// Returns interface orientation masks. (返回最优先显示的屏幕方向)// 同时支持Portrait和Landscape方向,但想优先显示Landscape方向,那软件启动的时候就会先显示Landscape,在手机切换旋转方向的时候仍然可以在Portrait和Landscape之间切换;// 返回现在正在显示的用户界面方向- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {};

如果返回nil,那就使用系统默认的转场动画.

** 屏幕旋转设置注意点:**在iOS 4 and 5,都是由具体的view controller来决定对应的view的orientation设置。而在iOS 6,则是由 top-most controller来决定 view 的 orientation 设置。

在PopAnimator的transitionDuration:方法中设置好时间.设定转场动画的内容

举个例子:你的app的rootViewController是navigation controller "nav", 在”nav"里的stack依次是:main view -> sub view > sub sub view,而main view里有一个button会present modal view "modal view".

 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // 获得容器 let containerView = transitionContext.containerView()! // 获得目标view // viewForKey 获取新的和老的控制器的view // viewControllerForKey 获取新的和老的控制器 let toView = transitionContext.viewForKey(UITransitionContextToViewKey)! containerView.addSubview toView.alpha = 0.0 UIView.animateWithDuration(duration, animations: { () -> Void in toView.alpha = 1.0 }) {  -> Void in // 转场动画完成 transitionContext.completeTransition } }

那么for ios 4 and 5,在ipad里,如果你要上述view都仅支持横屏orientation,你需要在上面的main view, sub view, sub sub view, model view里都添加:

内容是:通过修改透明度,达到一个渐变的效果,如下图所示.

- shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation==UIInterfaceOrientationLandscapeRight); } 

星彩网app下载 6

而对于iOS6, 由于是由 top-most controller 来设置 orientation,因此你在main view, sub view, sub sub view里添加下面的代码是没有任何效果的,而应该是在nav controller里添加下列代码。而modal view则不是在nav container里,因此你也需要在 modal view controller 里也添加下列代码。

这样一个简单的转场效果就实现啦!是不是很简单,只是这还不是我们想要的效果.

- shouldAutorotate { return YES; } -(NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskLandscape; } 

注意:这一步是在第3步的基础上修改的.在设置PopAnimator中添加 var presenting = true.用于判断到底是弹出控制器,还是后退,因为前进和后退都会调用animateTransition这个代理方法.下一个实现效果虽然用不到,但我们先这样去设置.还要设置一个 var originFrame = CGRect.zero 用于设置目的控制器的frame,将animateTransition中原来的代码更改为如下

** 注意:**你需要自定义一个UINavigationController的子类for "nav controller",这样才可以添加上述代码。和navigation controller类似,tab controller里的各个view的orientation设置应该放在tab controller里

func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // 获得容器 let containerView = transitionContext.containerView()! // 获得目标view // viewForKey 获取新的和老的控制器的view // viewControllerForKey 获取新的和老的控制器 let toView = transitionContext.viewForKey(UITransitionContextToViewKey)! let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)! // 拿到需要做动画的view let herbView = presenting ? toView : fromView // 获取初始和最终的frame let initialFrame = presenting ? originFrame : herbView.frame let finalFrame = presenting ? herbView.frame : originFrame // 设置收缩比率 let xScaleFactor = presenting ? initialFrame.width / finalFrame.width : finalFrame.width / initialFrame.width let yScaleFactor = presenting ? initialFrame.height / finalFrame.height : finalFrame.height / initialFrame.height let scaleTransform = CGAffineTransformMakeScale(xScaleFactor, yScaleFactor) // 当presenting的时候,设置herbView的初始位置 if presenting { herbView.transform = scaleTransform herbView.center = CGPoint(x: CGRectGetMidX(initialFrame), y: CGRectGetMidY(initialFrame)) herbView.clipsToBounds = true } containerView.addSubview // 保证在最前,不然添加的东西看不到哦 containerView.bringSubviewToFront // 加了个弹性效果 UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.0, options: [], animations: { () -> Void in herbView.transform = self.presenting ? CGAffineTransformIdentity : scaleTransform herbView.center = CGPoint(x: CGRectGetMidX(finalFrame), y: CGRectGetMidY(finalFrame)) }) {  -> Void in transitionContext.completeTransition } }

** api 说明:**** attemptRotationToDeviceOrientation 使用说明:**

效果如下:

// call this method when your return value from shouldAutorotateToInterfaceOrientation: changes // if the current interface orientation does not match the current device orientation, a rotation may occur provided all relevant view controllers now return YES from shouldAutorotateToInterfaceOrientation: // 该方法的使用场景是 interface orientation和device orientation 不一致,但希望通过重新指定 interface orientation 的值,立即实现二者一致;// 如果这时只是更改了支持的 interface orientation 的值,没有调用attemptRotationToDeviceOrientation,那么下次 device orientation 变化的时候才会实现二者一致,关键点在于能不能立即实现。  attemptRotationToDeviceOrientation { // code}

星彩网app下载 7

举个例子:假设当前的 interface orientation 只支持 Portrait。如果 device orientation 变成 Landscape,那么 interface orientation 仍然显示 Portrait;如果这时我们希望 interface orientation 也变成和 device orientation 一致的 Landscape,以iOS 6 为例,需要先将 supportedInterfaceOrientations 的返回值改成Landscape,然后调用 attemptRotationToDeviceOrientation方法,系统会重新询问支持的 interface orientation,已达到立即更改当前 interface orientation 的目的。

仔细观察发现无论是presentViewController还是dismissViewController视图都是从左上角弹出来的.因为我们将originFrame设置为CGRect.zero了.

** supportedInterfaceOrientations 使用说明: **此方法返回当前viewController 支持的方向. 但是, 只有两种情况下此方法才会生效:1、当前viewController是window的rootViewController.2、当前viewController是modal模式的. 即, 此viewController是被调用presentModalViewController 而显示出来的.

星彩网app下载 8星彩网app下载 9

在以上两种情况中,UIViewController.supportedInterfaceOrientations 方法会作用于前viewController和所有childViewController. 以上两种情况之外, UIKit并不会理会你的supportedInterfaceOrientations 方法.

刚刚我们把originFrame设置为了CGRect.zero,但我们可以拿到目标控制器的原始尺寸啊,这样就不会突兀的从左上角弹出来了.

- (NSUInteger)supportedInterfaceOrientations{ return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;}
1.在ViewController的extension里面添加修改如下代码
 func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { transition.originFrame = selectedImage!.superview!.convertRect(selectedImage!.frame, toView: nil) transition.presenting = true selectedImage!.hidden = true return transition } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { transition.presenting = false return transition }

如果某个viewController实现了以上方法. 则, 此viewController就支持竖方向和左旋转方向. 此viewController的所有childViewController也同时支持这两个方向, 不多不少.

2.当dismiss的时候,刚点击的图片会消失,因为我们在modal的时候设置为了隐藏,所以在dismiss完成后要将selectedImage显示出来.可以这样做,在PopAnimator中添加一个闭包
 var dismissCompletion: )?

然后在animateTransition的UIView.animateWithDuration完成闭包中调用

UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0.0, options: [], animations: { () -> Void in herbView.transform = self.presenting ? CGAffineTransformIdentity : scaleTransform herbView.center = CGPoint(x: CGRectGetMidX(finalFrame), y: CGRectGetMidY(finalFrame)) }) {  -> Void in if !self.presenting { self.dismissCompletion?() } transitionContext.completeTransition }

最后在ViewController中的viewDidLoad中实现

 transition.dismissCompletion = { self.selectedImage!.hidden = false }

3.由于scrollview上的图片有圆角,所以在转场动画中我们也要实现图片圆角与控制器直角的切换.在PopAnimator中的animateTransition方法中添加如下代码:

 // 设置圆角 let round = CABasicAnimation(keyPath: "cornerRadius") round.fromValue = !presenting ? 0.0 : 20.0/xScaleFactor round.toValue = presenting ? 0.0 : 20.0/xScaleFactor round.duration = duration / 2 herbView.layer.addAnimation(round, forKey: nil) herbView.layer.cornerRadius = presenting ? 0.0 : 20.0/xScaleFactor

最终自定义modal转场效果就完成啦!

星彩网app下载 10自定义modal转场动画

相比自定义modal转场动画,自定义push转场动画的场景不是很多,原理其实都差不多的.

push是需要导航控制器的,所以在AppDelegate中加载控制器的时候,给它套一个导航控制器.代码中,ViewController和DetailViewController就是对应切换的两个控制器.给ViewController添加一个手势监听进行跳转.

星彩网app下载 11

  1. 新建一个继承 NSObject, UIViewControllerAnimatedTransitioning的RevealAnimator文件,用于设置转场动画的代理方法,在里面添加UIViewControllerAnimatedTransitioning协议必须要实现的两个代理方法:
 let animationDuration = 2.0 // 用于判断push或者pop var operation: UINavigationControllerOperation = .Push // 设置转场动画持续时间 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0 } // 执行转场动画 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { }
  1. 在ViewController文件中的viewDidLoad方法中拿到导航控制器将它设置为代理
 navigationController?.delegate = self

在ViewController文件中添加一个转场动画控制器容器属性

 let transition = RevealAnimator()

在ViewController文件中添加一个extension,实现代理方法

extension ViewController : UINavigationControllerDelegate { /** - parameter navigationController: 拿到设置代理的导航控制器 - parameter operation: .Push .Pop - parameter fromVC: 原来的控制器 - parameter toVC: 目标控制器 - returns: 返回设置好的转场动画 */ func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { transition.operation = operation return transition }}

星彩网app下载 12

  1. 在RevealAnimator中添加一个变量保存animateTransition方法的transitionContext,以后会用到
 weak var storedContext: UIViewControllerContextTransitioning?

老样子在animateTransition添加一些初始化代码

 let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! ViewController let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! DetailViewController transitionContext.containerView()?.addSubview(toVC.view)
  1. 添加LOGO的放大动画1.新建一个RWLogoLayer文件,用贝塞尔曲线画出logo
import UIKitclass RWLogoLayer { class func logoLayer() -> CAShapeLayer { let layer = CAShapeLayer() layer.geometryFlipped = true let bezier = UIBezierPath() bezier.moveToPoint(CGPoint(x: 0.0, y: 0.0)) bezier.addCurveToPoint(CGPoint(x: 0.0, y: 66.97), controlPoint1:CGPoint(x: 0.0, y: 0.0), controlPoint2:CGPoint(x: 0.0, y: 57.06)) bezier.addCurveToPoint(CGPoint(x: 16.0, y: 39.0), controlPoint1: CGPoint(x: 27.68, y: 66.97), controlPoint2:CGPoint(x: 42.35, y: 52.75)) bezier.addCurveToPoint(CGPoint(x: 26.0, y: 17.0), controlPoint1: CGPoint(x: 17.35, y: 35.41), controlPoint2:CGPoint(x: 26, y: 17)) bezier.addLineToPoint(CGPoint(x: 38.0, y: 34.0)) bezier.addLineToPoint(CGPoint(x: 49.0, y: 17.0)) bezier.addLineToPoint(CGPoint(x: 67.0, y: 51.27)) bezier.addLineToPoint(CGPoint(x: 67.0, y: 0.0)) bezier.addLineToPoint(CGPoint(x: 0.0, y: 0.0)) bezier.closePath() layer.path = bezier.CGPath layer.bounds = CGPathGetBoundingBox(layer.path) return layer }}

星彩网app下载 13

  1. 设置RWLogoLayer的位置尺寸,分别添加到fromVC和toVC上在ViewController的viewDidAppear中添加
 logo.position = CGPoint(x: view.layer.bounds.size.width/2, y: view.layer.bounds.size.height/2   30) logo.fillColor = UIColor.whiteColor().CGColor view.layer.addSublayer

在DetailViewController的viewDidLoad中添加

maskLayer.position = CGPoint(x: view.layer.bounds.size.width/2, y: view.layer.bounds.size.height/2) view.layer.mask = maskLayer

这边有个注意点,记得添加

 override func viewDidAppear(animated: Bool) { super.viewDidAppear view.layer.mask = nil }

移除mask

  1. 设置动画,在RevealAnimator的animateTransition方法中添加
 let animation = CABasicAnimation(keyPath: "transform") animation.fromValue = NSValue(CATransform3D: CATransform3DIdentity) // 添加一个阴影效果 animation.toValue = NSValue(CATransform3D:CATransform3DConcat(CATransform3DMakeTranslation(0.0, -10.0, 0.0), CATransform3DMakeScale(150.0, 150.0, 1.0))) animation.duration = animationDuration animation.delegate = self animation.fillMode = kCAFillModeForwards animation.removedOnCompletion = false animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) // 同时添加到两个控制器上 toVC.maskLayer.addAnimation(animation, forKey: nil) fromVC.logo.addAnimation(animation, forKey: nil) // 给目的控制器设置一个渐变效果 let fadeIn = CABasicAnimation(keyPath: "opacity") fadeIn.fromValue = 0.0 fadeIn.toValue = 1.0 fadeIn.duration = animationDuration toVC.view.layer.addAnimation(fadeIn, forKey: nil)

星彩网app下载 14

到这里push的转场效果基本完成了,但会发现还有问题,在自定义modal转场动画的时候,当转场动画完成后 需要设置transitionContext.completeTransition,而这边也是,这样做是为了在pop的之前把该清理的都清理掉.so重写animationDidStop

 override func animationDidStop(anim: CAAnimation, finished flag: Bool) { if let context = storedContext { context.completeTransition(!context.transitionWasCancelled let fromVc = context.viewControllerForKey(UITransitionContextFromViewControllerKey) as! ViewController fromVc.logo.removeAllAnimations() } storedContext = nil }
  1. push已经完成啦!现在当我们点击左上角的start按钮返回时会发生崩溃,是因为我们没有在RevealAnimator的animateTransition方法中作判断,到底是push还是pop.定义一个属性判断是pop还是push,将animateTransition中push相关的代码放到push判断语句中去
 var operation: UINavigationControllerOperation = .Push

 if operation == .Push { // push }else { // pop }
  1. 给pop添加一个缩小的效果
 let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)! let toView = transitionContext.viewForKey(UITransitionContextToViewKey)! transitionContext.containerView()?.insertSubview(toView, belowSubview: fromView) UIView.animateWithDuration(animationDuration, delay: 0.0, options: .CurveEaseIn, animations: { fromView.transform = CGAffineTransformMakeScale(0.01, 0.01) }, completion: {_ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled

星彩网app下载 15

至此,就完成啦!不过离最终效果还有一定距离!

  1. 在ViewController底部添加一个label,设置好滑动解锁这几个字,这只是提醒,我们的会为整个view添加滑动手势的.
  2. 将ViewController的点击监听事件改为拖动事件.
 let pan = UIPanGestureRecognizer(target: self, action: Selector("didPan:")) view.addGestureRecognizer

 func didPan(recognizer: UIPanGestureRecognizer) { }
  1. 根据滑动的偏移量来调整转场动画的进度,也就是说转场动画要是可以交互的.之前所有的转场动画都是开始后,自动结束,不会随着人的交互而发生任何改变.所以原来的方法不能满足需要.这时需要在ViewController的extension中添加
 // 返回一个可以交互的转场动画 func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { if !transition.interactive { return nil } return transition }

在RevealAnimator中添加一个属性,用于设置是否可以交互

 // 是否需要交互 var interactive = false

4.在ViewController的didPan方法中添加手势识别,这边只处理一部分,其余的传到RevealAnimator中进行

 switch recognizer.state { case .Began: transition.interactive = true navigationController?.pushViewController(DetailViewController(), animated: true) default: transition.handlePan(recognizer) }

5.在RevealAnimator的handlePan方法中,根据偏移量计算progress

func handlePan(recognizer: UIPanGestureRecognizer) { let translation = recognizer.translationInView(recognizer.view!.superview!) var progress: CGFloat = abs(translation.x / 200.0) progress = min(max(progress, 0.01), 0.99) switch recognizer.state { case .Changed: // 更新当前转场动画播放进度 updateInteractiveTransition case .Cancelled, .Ended: if operation == .Push { // push let transitionLayer = storedContext!.containerView()!.layer transitionLayer.beginTime = CACurrentMediaTime() if progress < 0.5 { completionSpeed = -1.0 cancelInteractiveTransition() // 停止转场动画,回到from状态 } else { completionSpeed = 1.0 finishInteractiveTransition() // 完成转场动画,到to状态 } } else { // pop if progress < 0.5 { cancelInteractiveTransition() } else { finishInteractiveTransition() } } // 使得返回可交互的转场动画为nil,重置动画 interactive = false default: break } }

星彩网app下载 16自定义push转场动画

至此自定义push的转场动画也完成啦!

虽然转场动画不难,但从头到尾这一整套逻辑,还是有点繁琐的,可能有的同学看的有些蒙圈,这是很正常的,因为知识是网状的啊,线性的逻辑表述并不能表达清楚网状知识的每一个连接! so,我在下面给出了源码,给有兴趣推敲的同学.

本文整理自 : iOS.Animations.by.Tutorials.v2.0源码 : :-D

** preferredInterfaceOrientationForPresentation 使用说明: **此方法也仅有在当前viewController是rootViewController或者是modal模式时才生效.

** shouldAutorotate 使用说明:**用于设置当前viewController是否支持自动旋转. 如果,你需要viewController暂停自动旋转一小会儿. 那么可以通过这个方法来实现.同样的, 此方法也仅有在当前viewController是rootViewController或者是modal模式时才生效.

for ios6的top-most controller决定orientation设置,导致这样一个问题:*在 top-most controller里的views无法拥有不相同的orientation设置。*例如:for iphone, 在nav controller里,你有main view, sub view and sub sub view,前2个都只能打竖,而sub sub view是用来播放video,可以打横打竖。那么在ios 4 and 5里可以通过在main view and sub view的shouldAutorotateToInterfaceOrientation里设置只能打竖,而在sub sub view的shouldAutorotateToInterfaceOrientation设置打竖打横即可。而在ios 6里则无法实现这种效果,因为在main view, sub view and sub sub view的orientation设置是无效的,只能够在nav controller里设置。那么你可能想着用下列代码在nav controller里控制哪个view打竖,哪个view打横:

-(NSUInteger)supportedInterfaceOrientations{ if([[self topViewController] isKindOfClass:[SubSubView class]]) return UIInterfaceOrientationMaskAllButUpsideDown; else return UIInterfaceOrientationMaskPortrait; } 

是的,这样可以使得在main view and sub view里无法打横,而sub sub view横竖都行。但问题来了,如果在sub sub view时打横,然后back to sub view,那么sub view是打横显示的!

目前想到的解决方法只能是把sub sub view脱离nav controller,以modal view方式来显示。这样就可以在modal view里设置打横打竖,而在nav controller里设置只打竖。

说了那么多,其实如果你的app的所有view的orientation的设置是统一的,那么你可以简单的在plist file里设置即可,不用添加上面的代码。而如果你添加了上面的代码,就会覆盖plist里orientation的设置。

in iOS 6, 当view controller present时,不会call willRotateToInterfaceOrientation:duration:,willAnimateRotationToInterfaceOrientation:duration:,and didRotateFromInterfaceOrientation: methods,只有在发生rotate的时候才会call。

iOS两个强制旋转屏幕的方法有时候, 需要不随系统旋转, 而是强制旋转到某一个角度. 最典型的场景就是视频播放器, 当点击了全屏按钮的时候, 需要横过来显示.对于IOS5及以前的版本, 可以用下面的方法:

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) { SEL selector = NSSelectorFromString(@"setOrientation:"); NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]]; [invocation setSelector:selector]; [invocation setTarget:[UIDevice currentDevice]]; int val = UIInterfaceOrientationLandscapeRight; [invocation setArgument:&val atIndex:2]; [invocation invoke];}

对于IOS6及以后的版本. UIDevice.setOrientation从隐藏变为移除.只能通过设置UIView.transform 的方法来实现.

UIView.transform 最后一个方法是设置UIView 的transform属性来强制旋转.见下代码:

//设置statusBar[[UIApplication sharedApplication] setStatusBarOrientation:orientation];//计算旋转角度float arch;if (orientation == UIInterfaceOrientationLandscapeLeft) { rch = -M_PI_2; } else if (orientation == UIInterfaceOrientationLandscapeRight) { arch = M_PI_2;} else { arch = 0;}//对navigationController.view 进行强制旋转self.navigationController.view.transform = CGAffineTransformMakeRotation;self.navigationController.view.bounds = UIInterfaceOrientationIsLandscape(orientation) ? CGRectMake(0, 0, SCREEN_HEIGHT, SCREEN_WIDTH) : initialBounds;

需要注意的是:1、当然我们可以对当前viewController进行旋转, 对任何view旋转都可以.但是, 你会发现navigationBar还横在那里. 所以, 我们最好对一个占满全屏的view进行旋转. 在这里我们旋转的对象是self.navigationController.view, 当然self.window也可以, help yourself~2、我们需要显式的设置bounds. UIKit并不知道你偷偷摸摸干了这些事情, 所以没法帮你自动设置.

IOS6及以后对于IOS6及以后的版本, 如果想方便的单独控制每个viewController的方向. 则可以使用这样:对于非modal模式的viewController:如果不是rootViewController,则重写supportedInterfaceOrientations,preferredInterfaceOrientationForPresentation以及shouldAutorotate方法, 按照当前viewController的需要返回响应的值.如果是rootViewController,则如下重写方法:

-(NSUInteger)supportedInterfaceOrientations{ return self.topMostViewController.supportedInterfaceOrientations;}-shouldAutorotate{ return [self.topMostViewController shouldAutorotate];}- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{ return [self.topMostViewController preferredInterfaceOrientationForPresentation];}-(UIViewController*)topMostViewController{ //找到当前正在显示的viewController并返回.}

显而易见, 我们巧妙的绕开了UIKit只调用rootViewController的方法的规则. 把决定权交给了当前正在显示的viewController.对于modal模式的viewController. 则按照需要重写supportedInterfaceOrientations,preferredInterfaceOrientationForPresentation以及shouldAutorotate 方法即可.

四、强制屏幕旋转如果interface和device方向不一样,想强制将interface旋转成device的方向,可以通过attemptRotationToDeviceOrientation实现,但是如果想将interface强制旋转成任一指定方向,该方式就无能为力了。1、私有方法

[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait]; 

2、旋转view的transform也可以通过旋转view的transform属性达到强制旋转屏幕方向的目的,但个人感觉这不是靠谱的思路,可能会带来某些诡异的问题。

3、主动触发 orientation 机制要是能主动触发系统的 orientation 机制,调用 orientation 相关方法,使新设置的 orientation 值起作用就好了。这样只要提前设置好想要支持的 orientation,然后主动触发 orientation 机制,便能实现将 interface orientation旋转至任意方向的目的。

方式一、直接设置 UIDevice 的 orientation

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) { [[UIDevice currentDevice] performSelector:@selector(setOrientation:) withObject:UIInterfaceOrientationPortrait]; } 

方式二、没有改变 UIDevice 的 orientation,而是改变某个view的 transform,利用 CGAffineTransformMakeRotation 来达到目的,比如:

self.view.transform = CGAffineTransformMakeRotation 

下面讲解采用第二种方式的各版本手动旋转:思想是首先设置 statusBarOrientation,然后再改变某个view的方向跟 statusBarOrientation 一致!IOS6手动旋转:

  1. 那既然是旋转,最少也得有2个方向,那么还是少不了上面说的那个硬性条件,先在plist里面设置好所有可能需要旋转的方向。既然是手动旋转,那么就要关闭自动旋转:
- shouldAutorotate{ return NO; } 

2.手动触发某个按钮,调用方法,这个方法的实现如下:

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight]; self.view.transform = CGAffineTransformMakeRotation; self.view.bounds = CGRectMake(0, 0, kScreenHeight, 320); 

注意:

  1. 只需要改变self.view.transform,那么self.view的所有subview都会跟着自动变;其次因为方向变了,所以self.view的大小需要重新设置,不要使用self.view.frame,而是用bounds。
  2. 如果shouldAutorotate 返回YES的话,下面设置setStatusBarOrientation 是不管用的!setStatusBarOrientation只有在shouldAutorotate 返回NO的情况下才管用!

IOS5、IOS4手动旋转:

  • shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{return (interfaceOrientation == [UIApplication sharedApplication].statusBarOrientation);}1.在需要手动旋转的viewController里的 shouldAutorotateToInterfaceOrientation 方法设置 interfaceOrientation == [UIApplicationsharedApplication].statusBarOrientation
- shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{ return (interfaceOrientation == [UIApplication sharedApplication].statusBarOrientation); } 

2.手动触发某个按钮,调用方法,这个方法的实现如下:

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight]; self.view.transform = CGAffineTransformMakeRotation; self.view.bounds = CGRectMake(0, 0, kScreenHeight, 320); 

注意:只需要改变self.view.transform,那么self.view的所有subview都会跟着自动变;其次因为方向变了,所以self.view的大小需要重新设置,不要使用self.view.frame,而是用bounds。

经验分享:1.IOS6里面,如果一个项目里面需要各种旋转支持,有自动,有手动,那么我们可以新建2个navController或者tabbarController的子类,一个是不旋转,一个旋转,那么所有需要旋转的UINavigationController都可以用这个子类来代替!包括我们可以定制短信呀、邮件呀的旋转!2.supportedInterfaceOrientations 方法一般是写UIInterfaceOrientationMask方向,但是如果程序要兼容4.3以下的SDK(4.3以下的SDK必须是4.5以下的Xcode,不支持IOS6),那么在用4.5以下的Xcode编译的时候通不过!所以可以用statusBarOrientation代替或者直接写死数字!

-(NSUInteger)supportedInterfaceOrientations{ return [UIApplication sharedApplication].statusBarOrientation; } 

3.一般都不建议在程序里面直接调用 UIDeviceOrientation 的方向,而是用 UIInterfaceOrientation,他们之间是不同的!

UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft 

UIApplication.h

// Note that UIInterfaceOrientationLandscapeLeft is equal to UIDeviceOrientationLandscapeRight (and vice versa).// This is because rotating the device to the left requires rotating the content to the right.typedef NS_ENUM(NSInteger, UIInterfaceOrientation) { UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown, UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait, UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft} __TVOS_PROHIBITED;/* This exception is raised if supportedInterfaceOrientations returns 0, or if preferredInterfaceOrientationForPresentation returns an orientation that is not supported.*/UIKIT_EXTERN NSString *const UIApplicationInvalidInterfaceOrientationException NS_AVAILABLE_IOS __TVOS_PROHIBITED;typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) { UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait), UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft), UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight), UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown), UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight), UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown), UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),} __TVOS_PROHIBITED;

以组合的方式更加方便的使用这些枚举值

万能的stackoverflow上提供了一种主动触发的方式:UIViewController *vc = [[UIViewController alloc]init]; [self presentModalViewController:vc animated:NO]; [self dismissModalViewControllerAnimated:NO]; [vc release]; 

本文由星彩网app下载发布于计算机编程,转载请注明出处:iOS8中屏幕旋转的问题,可以很酷的转场动画

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。