美文网首页iOS UI布局相关
从横屏适配看布局

从横屏适配看布局

作者: 氮化镓加砷 | 来源:发表于2017-10-23 10:52 被阅读30次

以前的很多老代码都不支持横屏,并且没有用约束去布局,各种手动的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关系去适配,但是如果要优雅的适配横屏,优化整体的构架是唯一选择。

相关文章

  • 从横屏适配看布局

    以前的很多老代码都不支持横屏,并且没有用约束去布局,各种手动的frame布局导致适配起来很困难,再加上View层级...

  • 使用Size Class实现横坚屏界面适配

    最近工作中碰到了横坚屏切换且界面差异较大的需求。最常用的实现方式是通过实现两个不同的布局View来适配横屏与坚屏。...

  • iOS横屏和竖屏布局不同的实现方案浅析

    横屏和竖屏布局不同的实现方案浅析最近在开发中碰到了需要兼容横竖屏的布局需求,由于要兼容iOS7且要适配目前所有的i...

  • 横竖屏切换时, 容易出现的问题

    iPhone X适配时, 切换从横屏到竖屏的时候, 竖屏下面的scrollview会滑动44px, 很神奇, 不要...

  • ios 屏幕旋转适配(横屏适配)

    屏幕的横竖屏适配有很多种,今天写直播demo的时候发现给播放器一套约束或者frame,自动切换横屏的时候布局混乱拉...

  • rem.js

    根据设计尺寸制作rem适配增加横屏判断

  • MBProgressHUD横屏适配

    在对程序横屏适配的时候,发现MBProgressHUD会出现显示方向异常在竖屏状态是正常显示;但在横屏状态,方向还...

  • 魅族Flyme7系统刘海屏app适配文档

    一, 需求 竖屏有状态栏时由状态栏调整其布局来适配刘海区 横屏或竖屏无状态栏时默认情况下由系统平移界面来避开刘海区...

  • 网格的创建和适配

    网格的适配横屏竖屏 注意适配加入iOS第三方 Masonry 图片定义从零开始 //AppDelegate.m #...

  • iOS横屏(单页)

    项目中会遇到一页是横屏,其他保持竖屏的情况一.在横屏的页面用autoResizing布局: 二.总体设置禁止横屏:...

网友评论

    本文标题:从横屏适配看布局

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