文章分享至個人我的技術博客: https://cainluo.github.io/15101116434794.htmlhtml
隨着蘋果爸爸愈來愈多尺寸的設備發佈, 還有iOS
設計的改變, 特別是在iOS 11
以後, 比大更大的導航欄, 而後再滾動的時候能夠改變大小等等操做.ios
但這些問題都不是什麼問題, 就如同在WWDC 2017
同樣, 蘋果爸爸在跟咱們開發者展現同樣東西, 也是他一直想咱們去使用的東西, 那就是自動佈局.git
隨着什麼10.5英寸, 5.8英寸, 12.9英寸這些設備的發佈, 讓咱們開發者在適配多個尺寸的時候也愈來愈麻煩了, 但隨着使用自動佈局的發佈可讓咱們開發者更加註重App
的業務上的開發, 不再用去計算這個差多少, 那個差多少, 旋轉一下又怎麼處理.github
這裏咱們就使用一個簡單的小項目來搗鼓就行了.緩存
轉載聲明:如須要轉載該文章, 請聯繫做者, 而且註明出處, 以及不能擅自修改本文.安全
在iOS 11
裏, 最明顯的變化就是導航欄裏多了一個大標題:微信
這裏咱們能夠經過設置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
, 這樣子區分也能夠:
而第二個變化就是在UINavigationController
裏集成了UISearchController
, 雖然UISearchController
並非什麼新的東西, 但在iOS 11
以後, 咱們能夠將UINavigationItem
的searchController
屬性設置爲UISearchController
.
UISearchController *searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.navigationItem.searchController = searchController;
self.navigationItem.hidesSearchBarWhenScrolling = YES;
複製代碼
這裏還有一個屬性叫作hidesSearchBarWhenScrolling
, 若是設置爲YES
的話, 那麼在操做的時候, 就會根據滑動來隱藏這個searchController
, 若是設置爲NO
, 就一直顯示着啦, 這個屬性默認是爲YES
.
相信iOS 11
發佈的時候, 不少人都有一個黑人問號的表情, 安全區域(Safe area)是個什麼鬼.
其實早在iOS 7
出來的時候就有兩個屬性topLayoutGuide
和bottomLayoutGuide
, 這兩個屬性用於自動佈局, 因爲在iOS 7
的時候引入了半透明的概念, 若是咱們用UITableView
這類控件佈局的話, 那麼在UINavigationBar
和UITabBar
的背後會顯示內容.
而咱們設置了topLayoutGuide
和bottomLayoutGuide
就不會有這個問題了, 但遺憾的是, 這兩個屬性是屬於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
就會將在以前在頂部和底部的佈局約束自動轉換到安全區域中.
但有一個狀況是須要本身手動的搗鼓的, 這個時候咱們就要取消Use Safe Area Layout Guides
, 而後再手動佈局, 而且把以前相對於安全區域的約束雙擊, 而後設置爲SuperView
:
注意: 若是咱們以前對
topLayoutGuide
和bottomLayoutGuide
設置了約束, 而這個時候咱們把Use Safe Area Layout Guides
勾掉, 那麼就會和剛剛說的那樣,UINavigationBar
和UITabBar
的背後會顯示內容, 若是咱們要限制一下視圖到頂部的話, 咱們應該在視圖的topAnchor
和topLayoutGuide.bottomAnchor
添加一個約束, 可是在iOS 11
中, 咱們能夠在topAnchor
和safeAreaLayoutGuide.topAnchor
之間添加一個約束就哦了. PS:topLayoutGuide
指示狀態欄和導航欄覆蓋的區域.safeAreaLayoutGuide
指示狀態欄和導航欄未覆蓋的區域
Margins
也有一些新的變化, 好比有些屬性被棄用了:
被代替:
@property (nonatomic) UIEdgeInsets layoutMargins;
複製代碼
代替的屬性:
@property (nonatomic) NSDirectionalEdgeInsets directionalLayoutMargins;
複製代碼
新的directionalLayoutMargins
是容許改變閱讀方向, 用了leading
和trailing
代替了left
和right
, 雖然仍然是用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
, 使用Storyboard
加代碼去實現.
ScrollView
的佈局:
UIImageView
的佈局:
TipsView
的佈局:
PS: 若是你的約束感受不對的話, 只要雙擊約束, 就能夠進入到裏面去修改了.
這裏的代碼也不難, 主要就是針對TipsView
和ScrollView
的佈局:
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;
複製代碼
若是想更詳細的查看約束, 能夠自行打開Demo
慢慢研究哈~.
在iOS 7
推出的時候, UITableView
就有一個屬性叫作:
@property (nonatomic) CGFloat estimatedRowHeight;
複製代碼
咱們能夠經過設置爲UITableViewAutomaticDimension
, 再給Cell
的內部視圖作好適配的約束, 那麼Cell
就能夠自適應了.
但這些都是須要咱們本身手動去適配, 在iOS 11
時, 這個東西已經不須要咱們去寫了, 默認就是UITableViewAutomaticDimension
.
並且這個屬性不僅僅只是對普通的Cell
有用, 包括SectionHeader
和SectionFooter
一樣都有效, 若是你不須要的話, 能夠手動把estimatedRowHeight
設置爲0.
若是你的項目是使用比較老的Xcode
, 而且是使用Storyboard
搗鼓的話, 你能夠打開對應的Storyboard
找到UITableView
, 而後找到對應的屬性, 勾上Automatic
:
可是大規模的使用自動佈局會形成一個性能上的問題, 這個以前也說過了, 那要怎麼作呢? 咱們能夠把estimatedRowHeight
設置爲大概要顯示多高的數值, 而後肯定好Cell
內的全部約束, 這樣子UITableView
的性能就會獲得改善, 固然最好的方式仍是使用異步加載, 或者是高度緩存之類的, 這些資料的話, 你們能夠自行去百度搜搜.
若是咱們給對應的UITableViewController
添加UIRefreshControl
的話, 那麼它會自動加載到UINavigationBar
中:
代碼也是很簡單:
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];
});
}
複製代碼
咱們都知道在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));
複製代碼
Cell
的邊緣值, 也就是0.咱們都知道了在iOS 9
的時候發佈了一個靈活佈局的堆棧視圖UIStackViews
, 有了它咱們就不須要管理大量的約束.
但有一些場景仍是沒有適應到了, 如今就能夠解決這個問題了, 在iOS 11
時, 蘋果爸爸給它增長了更多的特性, 好比咱們在多個視圖裏, 有一個視圖比較特殊, 要離別的地方比較遠, 以前是不可實現的, 如今能夠了, 讓咱們來看看吧:
詳細的約束佈局就麻煩你們去工程裏看看吧, 這裏就很少說了, 直接看代碼:
[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
版本里, 若是咱們要使用矢量圖, 那麼Xcode
和iOS
系統會將這個矢量圖在編譯的時候自動生成不一樣大小的圖片.
而在Xcode 9
中, 咱們能夠勾選一個東西, 告訴系統保留矢量數據:
這樣子的話, 當咱們或者是其餘用戶在輔助功能裏開啓的大字號字體時, 咱們就能夠經過設置UIImageView
的一個屬性來保證能夠正常顯示:
@property (nonatomic) BOOL adjustsImageSizeForAccessibilityContentSizeCategory;
複製代碼
在Demo
裏, 我只設置了一個圖標的屬性, 因此這個圖標是正常顯示, 而另外兩個是不正常的:
這個屬性能夠在Storyboard
找到, 也能夠在代碼裏實現, 只要是UIImageView
就ok
了.
最後就是補充一下給UINavigationBar
或者是UIToolbar
添加自定義視圖了.
咱們仍是拿剛剛的那個太陽, 星星和月亮的來舉例子, 這裏只要一個UIImageView
和一個UILabel
就行了.
這個佈局和咱們去自定義UITableViewCell
差很少, 直接用約束搗鼓就完事, 並且還特別的簡單:
運行的話, 咱們能夠看到是正常顯示的, 但發現會有一個白色的背景色, 咱們只要找到對應的UIView
, 而後把顏色清理掉就ok了.
在iOS 11
有一些會影響到自動佈局的變化, 還有些新增的屬性來幫助咱們開發者更便捷的開發, 若是你以爲光看文章還不夠的話, 能夠去看看原汁原味的WWDC 2017
的介紹:
https://github.com/CainRun/iOS-11-Characteristic/tree/master/3.Layout