從橫屏適配看布局

以前的很多老代碼都不支持橫屏,并且沒有用約束去布局,各種手動的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閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • /* UIViewController is a generic controller base class th...
    DanDanC閱讀 1,843評論 0 2
  • ViewsBecause view objects are the main way your applicati...
    梁光飛閱讀 622評論 0 0
  • 更好的閱讀體驗,請到個人博客閱讀: iOS中的系統轉場 請忽略標題,??,本文記錄的是對下圖所示的Kind, Pre...
    CaryaLiu閱讀 2,375評論 0 1
  • 你說,和我在一起的時候會想起她,會很強烈的感覺到對她的虧欠。 我知道,你們在感情醉炙熱的時候就不得不分開,愛情卻沒...
    藍獨玫閱讀 436評論 0 0
  • 我寫過很多無聊的文字,現在也繼續在寫著。我寫朋友、寫家人、寫身邊發生的小趣事。我想用文字記錄下我對他們的喜愛。對,...
    露大曾閱讀 318評論 0 4