玩轉iOS開發:iOS 11 新特性《Layout的新特性》

文章分享至個人我的技術博客: https://cainluo.github.io/15101116434794.htmlhtml


隨着蘋果爸爸愈來愈多尺寸的設備發佈, 還有iOS設計的改變, 特別是在iOS 11以後, 比大更大的導航欄, 而後再滾動的時候能夠改變大小等等操做.ios

但這些問題都不是什麼問題, 就如同在WWDC 2017同樣, 蘋果爸爸在跟咱們開發者展現同樣東西, 也是他一直想咱們去使用的東西, 那就是自動佈局.git

隨着什麼10.5英寸, 5.8英寸, 12.9英寸這些設備的發佈, 讓咱們開發者在適配多個尺寸的時候也愈來愈麻煩了, 但隨着使用自動佈局的發佈可讓咱們開發者更加註重App的業務上的開發, 不再用去計算這個差多少, 那個差多少, 旋轉一下又怎麼處理.github

這裏咱們就使用一個簡單的小項目來搗鼓就行了.緩存

轉載聲明:如須要轉載該文章, 請聯繫做者, 而且註明出處, 以及不能擅自修改本文.安全

更大的標題

iOS 11裏, 最明顯的變化就是導航欄裏多了一個大標題:微信

1

這裏咱們能夠經過設置UINavigationBar的一個BOOL值屬性來決定是否顯示這個大標題:app

@property (nonatomic, readwrite, assign) BOOL prefersLargeTitles;
複製代碼

那若是你是在Storyboard上的話, 你也能夠在UINavigationController上的UINavigationBar設置這個屬性, 勾上就是爲顯示大標題, 不勾上就不顯示.異步

雖然prefersLargeTitles是打開大標題的主開關, 但在每個控制器裏, 咱們均可以每一個控制器裏的UINavigationItem來顯示是否顯示大標題, 或者是普通標題, 這裏共有三種顯示類型:ide

typedef NS_ENUM(NSInteger, UINavigationItemLargeTitleDisplayMode) {
    UINavigationItemLargeTitleDisplayModeAutomatic,
    UINavigationItemLargeTitleDisplayModeAlways,
    UINavigationItemLargeTitleDisplayModeNever,
} NS_SWIFT_NAME(UINavigationItem.LargeTitleDisplayMode);
複製代碼

要使用這個屬性呢, 咱們得提早把prefersLargeTitles大標題屬性設置爲YES, 而後才能去搗鼓上面的三種顯示模式, 默認爲Automatic, 咱們能夠經過這個來設置控制器是否須要顯示大標題啦~

固然若是你不想每一個控制器都寫一遍, 那你能夠本身用RunTime寫個Method Swizzling, 或者是本身封裝一個RootController, 一個ChildController, 這樣子區分也能夠:

2

搜索控制器

而第二個變化就是在UINavigationController裏集成了UISearchController, 雖然UISearchController並非什麼新的東西, 但在iOS 11以後, 咱們能夠將UINavigationItemsearchController屬性設置爲UISearchController.

UISearchController *searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
    
    self.navigationItem.searchController = searchController;
    self.navigationItem.hidesSearchBarWhenScrolling = YES;
複製代碼

3

這裏還有一個屬性叫作hidesSearchBarWhenScrolling, 若是設置爲YES的話, 那麼在操做的時候, 就會根據滑動來隱藏這個searchController, 若是設置爲NO, 就一直顯示着啦, 這個屬性默認是爲YES.

4

安全區域

相信iOS 11發佈的時候, 不少人都有一個黑人問號的表情, 安全區域(Safe area)是個什麼鬼.

其實早在iOS 7出來的時候就有兩個屬性topLayoutGuidebottomLayoutGuide, 這兩個屬性用於自動佈局, 因爲在iOS 7的時候引入了半透明的概念, 若是咱們用UITableView這類控件佈局的話, 那麼在UINavigationBarUITabBar的背後會顯示內容.

而咱們設置了topLayoutGuidebottomLayoutGuide就不會有這個問題了, 但遺憾的是, 這兩個屬性是屬於Controller而不是屬於UIView, 爲了解決這個問題, 蘋果爸爸搗鼓了一個新的東西, 就是如今的安全區域, 一個名爲safeAreaLayoutGuide的東西, 是屬於UIVIew的.

@property(nonatomic,readonly,strong) UILayoutGuide *safeAreaLayoutGuide;
複製代碼

safeAreaLayoutGuide很適合咱們在安全區域裏建立約束, 這樣子咱們就能夠很是簡單的適配各式各樣的機型, 好比強調安全區域的iPhone X.

若是咱們只是想要測量一下這個安全區域, 那麼safeAreaInsets就會給咱們返回一些值.

typedef struct UIEdgeInsets {
    CGFloat top, left, bottom, right;
} UIEdgeInsets;
複製代碼

若是咱們想知道這些值是在何時改變的, 咱們能夠經過系統提供的兩個API:

// UIView的API
- (void)safeAreaInsetsDidChange;

// UIViewController
- (void)viewSafeAreaInsetsDidChange;
複製代碼

若是咱們不去修改這個安全區域的話, 咱們從safeAreaInsets得到的值都是爲0, 若是咱們手動去修改的話, 就能夠給動一動additionalSafeAreaInsets這個屬性:

self.additionalSafeAreaInsets = UIEdgeInsetsMake(100, 50, 0, 0);
複製代碼

而後在看看效果:

若是你是使用storyboard的話, 你能夠隨便點擊一個控件或者是控制器, 而後查看是否勾上了Use Safe Area Layout Guides, 若是勾上了的話, 那麼Xcode就會將在以前在頂部和底部的佈局約束自動轉換到安全區域中.

6

但有一個狀況是須要本身手動的搗鼓的, 這個時候咱們就要取消Use Safe Area Layout Guides, 而後再手動佈局, 而且把以前相對於安全區域的約束雙擊, 而後設置爲SuperView:

7

8

注意: 若是咱們以前對topLayoutGuidebottomLayoutGuide設置了約束, 而這個時候咱們把Use Safe Area Layout Guides勾掉, 那麼就會和剛剛說的那樣, UINavigationBarUITabBar的背後會顯示內容, 若是咱們要限制一下視圖到頂部的話, 咱們應該在視圖的topAnchortopLayoutGuide.bottomAnchor添加一個約束, 可是在iOS 11中, 咱們能夠在topAnchorsafeAreaLayoutGuide.topAnchor之間添加一個約束就哦了. PS: topLayoutGuide指示狀態欄和導航欄覆蓋的區域. safeAreaLayoutGuide指示狀態欄和導航欄未覆蓋的區域

9

10

邊距

Margins也有一些新的變化, 好比有些屬性被棄用了:

被代替:

@property (nonatomic) UIEdgeInsets layoutMargins;
複製代碼

代替的屬性:

@property (nonatomic) NSDirectionalEdgeInsets directionalLayoutMargins;
複製代碼

新的directionalLayoutMargins是容許改變閱讀方向, 用了leadingtrailing代替了leftright, 雖然仍然是用layoutMarginsGuide

當咱們設置directionalLayoutMargins的時候, 它的值是會被添加到systemMinimumLayoutMargins中, 用來肯定視圖的實際邊距, 若是咱們不想要系統的最小邊距, 咱們能夠把viewRespectsSystemMinimumLayoutMargins設置爲NO就能夠了.

最後一點補充的一個屬性爲:

@property (nonatomic) BOOL insetsLayoutMarginsFromSafeArea;
複製代碼

這個屬性若是爲YES, 那麼咱們須要佈局的視圖邊距就相對於安全區域, 若是設置爲NO, 那咱們須要佈局的視圖邊距就相對於其餘視圖的邊距, 默認爲YES.

滾動視圖們

iOS 11以前, 若是咱們要對UIScrollView使用自動佈局, 咱們須要寫一些邏輯來肯定是約束UIScrollView的滾動視圖, 仍是它的內容區域, 有時候在添加約束的時就會容易發生約束錯誤的狀況, 好比在使用Storyboard佈局.

但在iOS 11時, 爲了解決這個問題, 蘋果爸爸給UIScrollView添加了兩個約束屬性:

@property(nonatomic,readonly,strong) UILayoutGuide *contentLayoutGuide;
@property(nonatomic,readonly,strong) UILayoutGuide *frameLayoutGuide;
複製代碼

這兩個屬性可讓咱們給UIScrollView添加約束時更加的精準, 但兩個屬性對於使用Storyboard的開發者來說應該不是一件好消息, 由於這兩個東西只能在代碼中使用.

除了上面兩個屬性歪, 還有另外一個東西會影響到UIScrollView內容區域:

@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets;
複製代碼

這個屬性默認爲YES, 有時候咱們的視圖不會顯示在UINavigationBar的底部就是這個屬性搞的鬼, 把它設置爲NO就行了.

但慶幸的是, 在iOS 11這個屬性被幹掉了, 系統也再也不自動去設置UIScrollView的內容, 如今UIScrollView的內容插入調整是從安全區域和咱們在contentInset設置的值計算得出, 由如下屬性控制, 暫時共有四種控制方式:

@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior;

typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {
    UIScrollViewContentInsetAdjustmentAutomatic,
    UIScrollViewContentInsetAdjustmentScrollableAxes,
    UIScrollViewContentInsetAdjustmentNever,
    UIScrollViewContentInsetAdjustmentAlways,
} API_AVAILABLE(ios(11.0),tvos(11.0));
複製代碼

Demo

爲了更加好理解, 這裏展現一個Demo, 使用Storyboard加代碼去實現.

ScrollView的佈局:

11

UIImageView的佈局:

12

TipsView的佈局:

13

14

PS: 若是你的約束感受不對的話, 只要雙擊約束, 就能夠進入到裏面去修改了.

15

16

這裏的代碼也不難, 主要就是針對TipsViewScrollView的佈局:

CGFloat scrollIndicatorMargin = 8;
    
    self.tipsView.layer.cornerRadius = 8;
    
    [self.tipsView.leadingAnchor constraintEqualToAnchor:self.scrollView.frameLayoutGuide.leadingAnchor
                                                constant:scrollIndicatorMargin].active = YES;
    
    [self.tipsView.trailingAnchor constraintEqualToAnchor:self.scrollView.frameLayoutGuide.trailingAnchor
                                                constant:-scrollIndicatorMargin].active = YES;
    
    [self.tipsView.bottomAnchor constraintEqualToAnchor:self.scrollView.frameLayoutGuide.bottomAnchor
                                                constant:-scrollIndicatorMargin].active = YES;
    
    self.additionalSafeAreaInsets = UIEdgeInsetsMake(0,
                                                     0,
                                                     self.tipsView.frame.size.height + scrollIndicatorMargin,
                                                     scrollIndicatorMargin);
    
    self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
複製代碼

17

若是想更詳細的查看約束, 能夠自行打開Demo慢慢研究哈~.

自適應的Cells

iOS 7推出的時候, UITableView就有一個屬性叫作:

@property (nonatomic) CGFloat estimatedRowHeight;
複製代碼

咱們能夠經過設置爲UITableViewAutomaticDimension, 再給Cell的內部視圖作好適配的約束, 那麼Cell就能夠自適應了.

但這些都是須要咱們本身手動去適配, 在iOS 11時, 這個東西已經不須要咱們去寫了, 默認就是UITableViewAutomaticDimension.

並且這個屬性不僅僅只是對普通的Cell有用, 包括SectionHeaderSectionFooter一樣都有效, 若是你不須要的話, 能夠手動把estimatedRowHeight設置爲0.

若是你的項目是使用比較老的Xcode, 而且是使用Storyboard搗鼓的話, 你能夠打開對應的Storyboard找到UITableView, 而後找到對應的屬性, 勾上Automatic:

18

可是大規模的使用自動佈局會形成一個性能上的問題, 這個以前也說過了, 那要怎麼作呢? 咱們能夠把estimatedRowHeight設置爲大概要顯示多高的數值, 而後肯定好Cell內的全部約束, 這樣子UITableView的性能就會獲得改善, 固然最好的方式仍是使用異步加載, 或者是高度緩存之類的, 這些資料的話, 你們能夠自行去百度搜搜.

刷新控制器

若是咱們給對應的UITableViewController添加UIRefreshControl的話, 那麼它會自動加載到UINavigationBar中:

19

代碼也是很簡單:

self.tableView.refreshControl = [[UIRefreshControl alloc] init];
    
    [self.tableView.refreshControl addTarget:self
                                      action:@selector(refreshControllerAction)
                            forControlEvents:UIControlEventValueChanged];
複製代碼
- (void)refreshControllerAction {
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        [self.tableView.refreshControl endRefreshing];
    });
}
複製代碼

UITableVIew的分割線

咱們都知道在iOS 7的時候, 蘋果爸爸就給UITableView的分割線添加了一些偏移, 但那時候只能經過separatorInset去設置, 但這種設置太過死板缺乏靈活性.

iOS 11以後, 蘋果爸爸又添加了一個屬性:

@property (nonatomic) UITableViewSeparatorInsetReference separatorInsetReference;

typedef NS_ENUM(NSInteger, UITableViewSeparatorInsetReference) {
    UITableViewSeparatorInsetFromCellEdges,    
    UITableViewSeparatorInsetFromAutomaticInsets
} API_AVAILABLE(ios(11.0), tvos(11.0));
複製代碼
  • UITableViewSeparatorInsetFromCellEdges: 默認值, 若是使用該屬性的話, 分割線的起始座標爲Cell的邊緣值, 也就是0.
  • UITableViewSeparatorInsetFromAutomaticInsets: 若是使用該屬性的話, 分割線的起始座標會帶上默認值, 好比左邊的偏移爲15.

20

堆棧視圖Stack Views

咱們都知道了在iOS 9的時候發佈了一個靈活佈局的堆棧視圖UIStackViews, 有了它咱們就不須要管理大量的約束.

但有一些場景仍是沒有適應到了, 如今就能夠解決這個問題了, 在iOS 11時, 蘋果爸爸給它增長了更多的特性, 好比咱們在多個視圖裏, 有一個視圖比較特殊, 要離別的地方比較遠, 以前是不可實現的, 如今能夠了, 讓咱們來看看吧:

21

詳細的約束佈局就麻煩你們去工程裏看看吧, 這裏就很少說了, 直接看代碼:

[UIViewPropertyAnimator runningPropertyAnimatorWithDuration:0.5
                                                          delay:0
                                                        options:UIViewAnimationOptionCurveEaseInOut
                                                     animations:^{
        
                                                         if (self.action) {
                                                             
                                                             [self.stackView setCustomSpacing:self.customSpacing
                                                                                    afterView:self.sunImageView];
                                                         } else {
                                                             
                                                             [self.stackView setCustomSpacing:0
                                                                                    afterView:self.sunImageView];
                                                         }
                                                         
                                                         self.action = !self.action;
                                                         
                                                     } completion:nil];
複製代碼

矢量圖

在之前的Xcode版本里, 若是咱們要使用矢量圖, 那麼XcodeiOS系統會將這個矢量圖在編譯的時候自動生成不一樣大小的圖片.

而在Xcode 9中, 咱們能夠勾選一個東西, 告訴系統保留矢量數據:

22

這樣子的話, 當咱們或者是其餘用戶在輔助功能裏開啓的大字號字體時, 咱們就能夠經過設置UIImageView的一個屬性來保證能夠正常顯示:

@property (nonatomic) BOOL adjustsImageSizeForAccessibilityContentSizeCategory;
複製代碼

Demo裏, 我只設置了一個圖標的屬性, 因此這個圖標是正常顯示, 而另外兩個是不正常的:

23

這個屬性能夠在Storyboard找到, 也能夠在代碼裏實現, 只要是UIImageViewok了.

自定義導航欄視圖

最後就是補充一下給UINavigationBar或者是UIToolbar添加自定義視圖了.

咱們仍是拿剛剛的那個太陽, 星星和月亮的來舉例子, 這裏只要一個UIImageView和一個UILabel就行了.

這個佈局和咱們去自定義UITableViewCell差很少, 直接用約束搗鼓就完事, 並且還特別的簡單:

24

運行的話, 咱們能夠看到是正常顯示的, 但發現會有一個白色的背景色, 咱們只要找到對應的UIView, 而後把顏色清理掉就ok了.

總結

iOS 11有一些會影響到自動佈局的變化, 還有些新增的屬性來幫助咱們開發者更便捷的開發, 若是你以爲光看文章還不夠的話, 能夠去看看原汁原味的WWDC 2017的介紹:

工程

https://github.com/CainRun/iOS-11-Characteristic/tree/master/3.Layout

最後

碼字很費腦, 看官賞點飯錢可好

微信

支付寶
相關文章
相關標籤/搜索