从横屏适配看布局

以前的很多老代码都不支持横屏,并且没有用约束去布局,各种手动的frame布局导致适配起来很困难,再加上View层级创建的不规范,各种controller的嵌套,导致适配起来很困难,最近遇到了不少的坑,回去看了官方的资料,捕获了不少东西。

屏幕快照 2017-10-20 下午4.35.59.png

autoresizingMask

虽然现在可以用约束,但是autoresizingMask还是很有用的。
When a view’s bounds change, that view automatically resizes its subviews according to each subview’s autoresizing mask.

typedef enum UIViewAutoresizing : NSUInteger {
    UIViewAutoresizingNone = 0,
    UIViewAutoresizingFlexibleLeftMargin = 1 << 0, //保持左边距
    UIViewAutoresizingFlexibleWidth = 1 << 1,//保持宽度比例
    UIViewAutoresizingFlexibleRightMargin = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin = 1 << 3,
    UIViewAutoresizingFlexibleHeight = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
} UIViewAutoresizing;

当bounds改变的时候会自动触发layoutsubview方法,但是autoresizesSubviews属性为NO时会阻止。

UIViewController

You rarely create instances of the UIViewController class directly. Instead, you create instances of UIViewController subclasses and use those objects to provide the specific behaviors and visual appearances that you need.
A view controller’s main responsibilities include the following:
Updating the contents of the views, usually in response to changes to the underlying data.
Responding to user interactions with views.
Resizing views and managing the layout of the overall interface.
官方建议我们不用代码创建UIViewController,UIViewController的基本作用是管理view树,处理交互,更新内容。

The view controller that is owned by the window is the app’s root view controller and its view is sized to fill the window.
root view controller的view会自动适配window
View controllers load their views lazily. Accessing the view property for the first time loads or creates the view controller’s views. There are several ways to specify the views for a view controller:
View controllers通过懒加载的方式创建views(storyboard和nib)
有几种方式创建view for view controller。
storyboard,nib,重写loadView方法。

Important

A view controller is the sole owner of its view and any subviews it creates. It is 
responsible for creating those views and for relinquishing ownership of them at 
the appropriate times such as when the view controller itself is released. If you use 
a storyboard or a nib file to store your view objects, each view controller object 
automatically gets its own copy of these views when the view controller asks for 
them. However, if you create your views manually, each view controller must have 
its own unique set of views. You cannot share views between view controllers.

一个view对象只能被一个controller持有,通过SB和nib创建的view会自动copy。

A view controller’s root view is always sized to fit its assigned space. For other views in your view hierarchy, use Interface Builder to specify the Auto Layout constraints that govern how each view is positioned and sized within its superview’s bounds. You can also create constraints programmatically and add them to your views at appropriate times. For more information about how to create constraints,
Root view将总是会自动适应 assigned space。

屏幕快照 2017-10-20 下午5.02.57.png

可以看到SB和nib中Autoresizing的设置,所以通常设置rootView的frame是无效的。

代码创建时候需要手动创建 self.view。
-(void)loadView{
    self.view = [[UIView alloc] initWithFrame:CGRectZero];
    self.view.backgroundColor = [UIColor redColor];
}

UIViewContentMode

Options to specify how a view adjusts its content when its size changes.
typedef enum UIViewContentMode : NSInteger {
    UIViewContentModeScaleToFill,
    UIViewContentModeScaleAspectFit,//按比例缩放,保持所有内容
    UIViewContentModeScaleAspectFill,//按比例缩放,截取内容
    UIViewContentModeRedraw,
    UIViewContentModeCenter,
    UIViewContentModeTop,
    UIViewContentModeBottom,
    UIViewContentModeLeft,
    UIViewContentModeRight,
    UIViewContentModeTopLeft,
    UIViewContentModeTopRight,
    UIViewContentModeBottomLeft,
    UIViewContentModeBottomRight
} UIViewContentMode;

横屏

-shouldAutorotate  //frist
-supportedInterfaceOrientations //last if shouldAutorotate return YES

typedef enum UIInterfaceOrientationMask : NSUInteger {
    UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
    UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
    UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
    UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
    UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
    UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight)
} UIInterfaceOrientationMask;

谨慎使用layoutSubviews

由于layoutSubviews的触发条件是bounds的改变,所以在屏幕方向改变的时候,会调用该方法,这并不意味就可以在里面去随意的改变子Views的frame从而达到适配效果,因为你并不知道当屏幕切换的时候该方法会被调用几次。

使用childViewContrller

在使用嵌套的Controller的时候,parentVC的view的改变并不会使得嵌套的控制器视图自动的改变。

    CustomVC *vc = [[CustomVC alloc] init];
    vc.view.frame = self.view.frame;
    [self.view addSubview:vc.view];
    self.customvc = vc;

当然你可以通过加入约束的方式去实现.


 [childView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.right.bottom.mas_equalTo(self.view);
    }];

childView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin;

一个更好的办法是通过设置childViewContrller,具体可以参考官方手册,这使得我们将parentVC当做一个高层的controller,类似于tabContrller和navigationController去管理自己的子控制器。

 CustomVC *vc = [[CustomVC alloc] init];
 vc.view.frame = self.view.frame;
 [self addChildViewController:vc];
 self.customvc = vc;

值得注意的另外一个问题是,当屏幕旋转的时候,viewDidLayoutSubviews和viewDidLayoutSubviews方法会被多次的调用。

2017-10-23 10:12:34.538597+0800 Masony_master[828:249531] parentVC:viewWillLayoutSubviews
2017-10-23 10:12:34.539323+0800 Masony_master[828:249531] parentVC:viewDidLayoutSubviews
2017-10-23 10:12:34.539496+0800 Masony_master[828:249531] childVC:viewWillLayoutSubviews
2017-10-23 10:12:34.539541+0800 Masony_master[828:249531] childVC:viewDidLayoutSubviews
2017-10-23 10:12:34.539589+0800 Masony_master[828:249531] childVC:viewWillLayoutSubviews
2017-10-23 10:12:34.539621+0800 Masony_master[828:249531] childVC:viewDidLayoutSubviews
2017-10-23 10:12:34.549540+0800 Masony_master[828:249531] parentVC:viewWillLayoutSubviews
2017-10-23 10:12:34.549987+0800 Masony_master[828:249531] parentVC:viewDidLayoutSubviews
2017-10-23 10:12:34.551653+0800 Masony_master[828:249531] parentVC:viewWillLayoutSubviews
2017-10-23 10:12:34.551773+0800 Masony_master[828:249531] parentVC:viewDidLayoutSubviews
2017-10-23 10:12:34.551841+0800 Masony_master[828:249531] childVC:viewWillLayoutSubviews
2017-10-23 10:12:34.551881+0800 Masony_master[828:249531] childVC:viewDidLayoutSubviews

所以如果一定要在这两个方法中做一些逻辑,一定要做预判断。

ChildViewController的另外一个优点是提供了一个简单的转场方法.

[self transitionFromViewController:(nonnull UIViewController *) toViewController:(nonnull UIViewController *) duration:<#(NSTimeInterval)#> options:<#(UIViewAnimationOptions)#> animations:^{
        
    } completion:^(BOOL finished) {
        
    }];

AutoLayout constraints循环产生bug

http://www.cocoachina.com/ios/20160725/17157.html中提出了使用AutoLayout有可能会产生layout循环,但是这个问题一般很难遇到。
通常来讲,如果子View和父View相关约束条件变化会导致父View调用layoutSubviews方法。如果这个时候我们又在layoutSubviews方法里面改变了子View的约束,那么循环就可能产生了。
或者,我们在子View的layoutSubView方法中手动的更改父view的尺寸并且强制使父View layout (setNeedLayout)。
所以在layoutSubviews方法里面更改父View的布局是一个愚蠢的做法,更安全的讲,在该方法里面更新自身或者子View的约束也是不安全的。

总结

对于目前为止一个优秀的设计是可以通过约束布局去实现的,这样无论屏幕怎么改变,view总会保持很好的尺寸,布局不会出现错误。
老的代码通常通过手动设置frame的方式布局,导致很难去适配横屏幕,特别是嵌套的控制器,如果每次屏幕转换的时候都去重新建立初始化控制器,那么效率和体验将会受到严重影响。
对于一些老的代码框架一时间无法改变的情况,我们又不能改变其核心代码,我们只能从外部加入约束,对于内部一些复杂的view,我们只能通过高层view的layoutSubview方法去补救。嵌套的控制器,通过加入parent-child关系去适配,但是如果要优雅的适配横屏,优化整体的构架是唯一选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • /* UIViewController is a generic controller base class th...
    DanDanC阅读 1,719评论 0 2
  • ViewsBecause view objects are the main way your applicati...
    梁光飞阅读 540评论 0 0
  • 更好的阅读体验,请到个人博客阅读: iOS中的系统转场 请忽略标题,😂,本文记录的是对下图所示的Kind, Pre...
    CaryaLiu阅读 2,287评论 0 1
  • 你说,和我在一起的时候会想起她,会很强烈的感觉到对她的亏欠。 我知道,你们在感情醉炙热的时候就不得不分开,爱情却没...
    蓝独玫阅读 410评论 0 0
  • 我写过很多无聊的文字,现在也继续在写着。我写朋友、写家人、写身边发生的小趣事。我想用文字记录下我对他们的喜爱。对,...
    露大曾阅读 287评论 0 4