iPad横竖屏下的代码适配

作者: jmstack | 来源:发表于2016-05-07 10:38 被阅读2355次

    原文地址在我的个人主页

    你可能非常了解用不同的方式去适配不同尺寸的iPhone屏幕,在适配iPhone屏幕时你需要考虑的只是屏幕大小变化带来的UI元素间隔的变化,但是在iPad上主要针对的是横竖屏下完全不同的UI元素的布局,在这种情况下要考虑的就不仅仅是元素之间间隔这种问题了,除了要确保UI元素在这两种模式下的正确显示还要兼顾屏幕旋转的过渡动画.下图是QQZone For iPad 在横竖屏下的布局,可以看到横竖屏下菜单栏的布局方式差别很大.

    QQZone for iPad GitHub地址

    QQZone for iPad 竖屏
    QQZone for iPad 横屏

    屏幕适配的N种方法

    无论iPad还是iPhone适配不同屏幕(尺寸,方向)的方式都跑不出以下几种,以下会一一对不同方式做一下简单的回顾.

    Autoresizing

    Autoresizing可以说是Autolayout始祖,Autoresizing的是一项比较有历史的技术了,其在iOS2的时代就推出了.当设置UIView实例对象的autoresizesSubviews属性为true(默认值为true),那么其子view会根据自已的autoresizingMask属性值自动调整与superview的位置和大小关系.autoresizingMask有六种可组合的使用的值,默认值是.None.这六种有效枚举值的意思如下:

    • FlexibleLeftMargin 按比例跟随父控件变化的左间距
    • FlexibleWidth 按比例跟随交控件变化的宽度
    • FlexibleRightMargin 按比例跟随父控件变化的右间距
    • FlexibleTopMargin 按比例跟随父控件变化的顶部间距
    • FlexibleHeight 按比例跟随控件变化的高度
    • FlexibleBottomMargin 按比例跟随父控件变化的底部间距

    另外在xib,storyboard取消Autolayout时(Autoresizing与Autolayout相互冲突)可以在Size inspector可以更加直观地按需求进行组和使用.Autoresizing技术在一定应用场景下可以勉强使用但应对更为精细的布局就无能为力了,你可以在使用Autoresizing同时重写layoutSubviews方法去做更为精细的布局,尽管如此但还是不推荐这么做,因为同时得写layoutSubviews和使用Autoresizing去布局会让你的布局逻辑变得不清晰,这将给后期的维护带来麻烦.

    Autoresizing in storyboard

    Autolayout

    Autolayout是iOS6时代引入的技术,专门用来处理不同屏幕尺寸下的UI布局.从Xcode6开始Autolayout配合xib,storyboard极大的提高屏幕的适配工作效率.在一定程度上甚至可以完全摆脱设置frame布局的方式.由于storyboard,xib在多人合作开发冲突不断的尴尬境地,在实际的开发中多使用第三方框架用代码进行Autolayout布局.这样既避免了解决冲突麻烦又享受到了Autolayout带来的宏利.比较受欢迎的Autolayout每三方框架有Masonry还有GSD_iOS大神的SDAutoLayout

    尽管Autolayout有很多好处但还是很多代码党不愿使用,究其原因还是约束.约束的问题大至可以分约束冲突和约束不满足两大类,当在storyboard中对一个复杂的界面进行Autolayout约束,一但出现问题将很难排查,用代码行约束往往程序运行起来才能确认约束是否满足条件,同样排查起来也不是那么方便.关于Autolayout这不再占用过多的篇幅,网上有相当多的资料可供参考.

    SizeClass

    SizeClass是要配合Autolayout使用的,SizeClass实际上是对屏幕尺寸的抽象,把屏幕宽高分成Compact:紧凑、Regular:宽松、Any:任意三种类型这样就可以组合出九种不同的屏幕类型.在storyboard,xib编辑界面下最下方可以选择某一约束在只在某一类屏幕下生效.这样可以在不同屏幕下得到不同的UI布局效果.关于SizeClass的使用可以参考raywenderlich系例文章.

    Demo预览慢速
    Demo预览正常

    Demo分析

    上面一些基础的知识将有助于理解Demo的做法,所以尽管有一点废话连篇的感觉好在也并不是一无是处.在讲解Demo的实现思路之前你可以在GitHub下载这个Demo,以便更方便的查看我讲到的代码.

    Demo中最复杂的,横竖屏布局变化最大的部分就是左侧的菜单栏可以称它为Dock栏,通过旋转屏幕可以看到原生QQZone HD的Dock栏的变化.可以根据变化的特征将整个Dock栏分为三部分.一是顶部的头像 二是中间的类TabBar,我称它为TabBar 三是 底部的快捷导航菜单.因此DockView的subview包含iconButton,tabBarView,menuBar三个,而这三个subview又可以分另包含各自的子控件.

    view层次结构

    实际上实现QQZone for iPad屏幕的横竖屏的布局并不复杂.一个view要知道怎样在layoutSubviews中去布局其子view只需知道当前其superview的状态(横竖屏).在这里我声明了一个协议,这个协议只包含一个获取当前view是否是竖屏的方法.让每一个需要根据横竖屏动态变化的view都实现这个协议的方法,这样在layoutSubviews方法就可以询问当前应该怎么样布局子控件而当前状态是由父控件状态决定的.由此形成了屏幕状态的传递链,使得每个veiw只关心自身直接subview的布局.

    // UIViewisPortrait协议
    protocol UIViewisPortrait: NSObjectProtocol {
        func isPortrait() -> Bool
    }
    

    根控制器view的任意subview可都可以通过如下代码获取当前是否是竖屏

    // subview 如何获取superview状态
    func isPortrait() -> Bool {
        guard let superview = superview else { // 如果不存大superview默认返回竖屏
            return true
        }
        return ((superview as? UIViewisPortrait)?.isPortrait())!
    }
    

    而根控制器view则直接通过宽高获取屏幕状态

    func isPortrait() -> Bool {
        return frame.width < frame.height
    }
    

    当前view知道是横竖屏后就可以直接在layoutsubviews布局子控件了,以menuBar为例

    override func layoutSubviews() {
        super.layoutSubviews()
        guard subviews.count > 0 else {
            return
        }
    
        var x, y, w, h:CGFloat
        for (index, view) in subviews.enumerate() {
            if isPortrait() == true {
                w = frame.width
                h = kDockItemHeight
                x = 0
                y = CGFloat(index) * h
                view.frame = CGRect(x: x, y: y, width: w, height: h)
            } else {
                w = frame.width / CGFloat(subviews.count)
                h = kDockItemHeight
                x = CGFloat(index) * w
                y = frame.height - h
                view.frame = CGRect(x: x, y: y, width: w, height: h)
            }
        }
    }
    
    

    其它类型的所有子控件都可以用类似的方法进行布局,如此你只需要定义一个view并始之成为某个View的subview,在你定义的view中你可以随意的获取屏幕状态布局子控件了.

    为了在根控制器View的layoutSubviewsr的方法中布局DockView,需要重写控制器的loadView方法,让控制器加载自定义的View. 如果你注意到原生QQZone for iPad的内容显示区域在横竖屏下的变化会发现在横坚屏下内容显示区域宽都是一样的,所以还需要在根控制器View中添加一个容器View以显示内容.

    class HomePageView: UIView, UIViewisPortrait {
        private lazy var dockView:DockView = DockView()
    
        lazy var contentView: UIView = {
            let contentView = UIView()
            contentView.backgroundColor = UIColor.whiteColor()
            // 作为容器View,子控制器的view将添加到容器view上
            self.addSubview(contentView)
            return contentView
        }()
    
        func isPortrait() -> Bool {
            return frame.width < frame.height
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            addSubview(dockView)
            dockView.backgroundColor = globalBackgroudColor
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
            dockView.frame.size.height = frame.height
            dockView.frame.size.width = isPortrait() ? kDockProtraitWidth : kDockLandscapeWidth
    
            let x: CGFloat = dockView.frame.width
            let y: CGFloat = 20
            // 无论横竖屏,内容视图的宽都一样
            let w: CGFloat = min(frame.width, frame.height) - kDockProtraitWidth
            let h: CGFloat = frame.height - y
    
            contentView.frame = CGRectMake(x, y, w, h)
        }
    
    }
    

    最后由于内容区域不用区分横竖屏,因此内容区域的子视图可以只考虑竖屏的情况,以上可以说得不是很清楚,如果感觉有兴趣可下载原码参阅.

    总结

    以上重点仅仅是用代码进行iPad横竖屏适配方法的探讨,这里只是记录了我认为较为合理的方法,当然这种方法可能并不适用所有的布局,毕竟每个App都有自己独特的UI部分.如果觉得这种方法不好欢迎指出,我将虚心请教.如果这个方法对你的业务提供了一点点的灵感希望点个赞,以上完.

    相关文章

      网友评论

      • d5c1645e0088:精彩的分享。将是否横竖屏沿着view hierarchy向subviews传递, 每个subview只关心自己的横竖屏排版

      本文标题:iPad横竖屏下的代码适配

      本文链接:https://www.haomeiwen.com/subject/niadrttx.html