以前的很多老代碼都不支持橫屏,并且沒有用約束去布局,各種手動的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關系去適配,但是如果要優雅的適配橫屏,優化整體的構架是唯一選擇。