以前的很多老代码都不支持横屏,并且没有用约束去布局,各种手动的frame布局导致适配起来很困难,再加上View层级创建的不规范,各种controller的嵌套,导致适配起来很困难,最近遇到了不少的坑,回去看了官方的资料,捕获了不少东西。
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。
可以看到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关系去适配,但是如果要优雅的适配横屏,优化整体的构架是唯一选择。