檢索詞條:safeAreaInsets iOS 11 安全距離 適配iPhoneX viewSafeAreaInsetsDidChange iOS 11安全距離什么時候被改變
環(huán)境 iOS 11 Xcode9.0 以上(這里也會介紹如果你和你的同事兩個人的Xcode一個是9.0以上 一個是9.0以下情況的解決辦法)
上一篇中我們講了safeAreaInsets的改變時機(jī) 及在不同機(jī)型上的變化 這里我們通過不同的示例來展示不同情況下的安全距離問題
看這個例子: ** 拖兩個自定義的 View, 這個 View 上有一個 顯示很多字的Label。然后設(shè)置這兩個 View 的約束分別是:
1.不考慮安全距離
// 不考慮安全距離
lable_1.frame = CGRectMake(0, 0, SCREEN_WIDTH, lable_1.height);
lable_2.frame = CGRectMake(0, SCREEN_HEIGHT - lable_2.height,SCREEN_WIDTH, lable_2.height);
可以看出來, 子視圖被頂部的劉海以及底部的 home 指示區(qū)擋住了竹伸。我們可以使用 frame 布局或者 auto layout 來優(yōu)化這個地方:
解決方案
- 方案一 在
viewSafeAreaInsetsDidChange
或之后的方法中布局這連個lable
- (void)viewSafeAreaInsetsDidChange{
[super viewSafeAreaInsetsDidChange];
// 考慮安全距離
UIEdgeInsets insets = self.view.safeAreaInsets;
_lable_1.frame = CGRectMake(insets.left, self.view.safeAreaInsets.top, SCREEN_WIDTH - insets.left - insets.right, _lable_1.height);
_lable_2.frame = CGRectMake(insets.left, SCREEN_HEIGHT - _lable_2.height - insets.bottom,SCREEN_WIDTH - insets.left - insets.right, _lable_2.height);
NSLog(@"viewSafeAreaInsetsDidChange__self.view.safeAreaInsets = %@",NSStringFromUIEdgeInsets(self.view.safeAreaInsets));
}
- 方案二 直接在自定義的 View 中修改 Label 的布局:
safeAreaInsetsDidChange
- (void)safeAreaInsetsDidChange{
NSLog(@"layoutSubviews__self.safeAreaLayoutGuide.layoutFrame = %@",NSStringFromCGRect(self.safeAreaLayoutGuide.layoutFrame));
CGFloat safeTop = self.safeAreaLayoutGuide.layoutFrame.origin.y;
CGFloat safeBottom = SCREEN_HEIGHT - self.safeAreaLayoutGuide.layoutFrame.size.height - safeTop;
_lable_1.frame = CGRectMake(0, safeTop, SCREEN_WIDTH, _lable_1.height);
}
這樣, 不僅僅是在 ViewController 中能夠使用 safe area 了间景。
UIViewController 中的 safe area
- 在 iOS 11 中 UIViewController 有一個新的屬性
@property(nonatomic) UIEdgeInsets additionalSafeAreaInsets API_AVAILABLE(ios(11.0), tvos(11.0));
當(dāng) view controller 的子視圖覆蓋了嵌入的子 view controller 的視圖的時候统倒。比如說不跟, 當(dāng) UINavigationController 和 UITabbarController 中的 bar 是半透明(translucent) 狀態(tài)的時候, 就有 additionalSafeAreaInsets
- (void)viewSafeAreaInsetsDidChange NS_REQUIRES_SUPER API_AVAILABLE(ios(11.0), tvos(11.0));
- (void)safeAreaInsetsDidChange API_AVAILABLE(ios(11.0),tvos(11.0));
這兩個方法分別是 UIView 和 UIViewController 的 safe area insets 發(fā)生改變時調(diào)用的方法甥温,如果需要做一些處理刷袍,可以重寫這個方法翩隧。有點(diǎn)類似于 KVO 的意思。
模擬 iPhone X 的 safe area
//豎屏
additionalSafeAreaInsets.top = 24.0
additionalSafeAreaInsets.bottom = 34.0
//豎屏, status bar 隱藏
additionalSafeAreaInsets.top = 44.0
additionalSafeAreaInsets.bottom = 34.0
//橫屏
additionalSafeAreaInsets.left = 44.0
additionalSafeAreaInsets.bottom = 21.0
additionalSafeAreaInsets.right = 44.0
UIScrollView 中的 safe area
iOS 7 中引入 UIViewController 的 automaticallyAdjustsScrollViewInsets 屬性在 iOS11 中被廢棄掉了呻纹。取而代之的是 UIScrollView 的 contentInsetAdjustmentBehavior
@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior API_AVAILABLE(ios(11.0),tvos(11.0));
這是一個枚舉
UIScrollViewContentInsetAdjustmentBehavior
typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {
UIScrollViewContentInsetAdjustmentAutomatic, // 默認(rèn)值
UIScrollViewContentInsetAdjustmentScrollableAxes, //
UIScrollViewContentInsetAdjustmentNever, //
UIScrollViewContentInsetAdjustmentAlways, //
} API_AVAILABLE(ios(11.0),tvos(11.0));
-
never
不做調(diào)整堆生。 -
scrollableAxes content insets
只會針對 scrollview 滾動方向做調(diào)整。 -
always content insets
會針對兩個方向都做調(diào)整雷酪。 -
automatic
這是默認(rèn)值淑仆。當(dāng)下面的條件滿足時, 它跟 always 是一個意思
能夠水平滾動哥力,不能垂直滾動
scroll view 是 當(dāng)前 view controller 的第一個視圖
這個controller 是被navigation controller 或者 tab bar controller 管理的
automaticallyAdjustsScrollViewInsets 為 true
在其他情況下automoatc 跟 scrollableAxes 一樣
Adjusted Content Insets
iOS 11 中 UIScrollView 新加了一個屬性:adjustedContentInset
@property(nonatomic, readonly) UIEdgeInsets adjustedContentInset API_AVAILABLE(ios(11.0),tvos(11.0));
- adjustedContentInset 和 contentInset 之間有什么區(qū)別呢蔗怠?
在同時有 navigation 和 tab bar 的 view controller 中添加一個 scrollview 然后分別打印兩個值:
//iOS 10
//contentInset = UIEdgeInsets(top: 64.0, left: 0.0, bottom: 49.0, right: 0.0)
//iOS 11
//contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
//adjustedContentInset = UIEdgeInsets(top: 64.0, left: 0.0, bottom: 49.0, right: 0.0)
然后再設(shè)置:
// 給 scroll view 的四個方向都加 10 的間距
scrollView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
打印:
//iOS 10
//contentInset = UIEdgeInsets(top: 74.0, left: 10.0, bottom: 59.0, right: 10.0)
//iOS 11
//contentInset = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
//adjustedContentInset = UIEdgeInsets(top: 74.0, left: 10.0, bottom: 59.0, right: 10.0)
由此可見,在 iOS 11 中 scroll view 實(shí)際的 content inset 可以通過 adjustedContentInset 獲取吩跋。這就是說如果你要適配 iOS 10 的話寞射。這一部分的邏輯是不一樣的。
系統(tǒng)還提供了兩個方法來監(jiān)聽這個屬性的改變
UIScrollView
- (void)adjustedContentInsetDidChange API_AVAILABLE(ios(11.0),tvos(11.0)) NS_REQUIRES_SUPER;
代理:
@protocol UIScrollViewDelegate<NSObject>
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView API_AVAILABLE(ios(11.0), tvos(11.0));
UITableView 中的 safe area
自定義的 header 上面有一個 lable锌钮,自定義的 cell 上面也有一個 label桥温。將屏幕橫屏之后會發(fā)現(xiàn),cell 以及 header 的布局均自動留出了 safe area 以外的距離梁丘。cell 還是那么大侵浸,只是 cell 的 contnt view 留出了相應(yīng)的距離。這其實(shí)是 UITableView 中新引入的屬性管理的:
insetsContentViewsToSafeArea
insetsContentViewsToSafeArea
的默認(rèn)值是 true氛谜, 將其設(shè)置成 no 之后:UICollectionView 中的 safe area
我們在做一個相同的 collection view 來看一下 collection view 中是什么情況:
這是一個使用了 UICollectionViewFlowLayout 的 collection view通惫。 滑動方向是豎向的。cell 透明混蔼, cell 的 content view 是白色的履腋。這些都跟上面 table view 一樣。header(UICollectionReusableView) 沒有 content view 的概念, 所以給其自身設(shè)置了紅色的背景。
從截圖上可以看出來遵湖, collection view 并沒有默認(rèn)給 header cell footer 添加safe area 的間距悔政。能夠?qū)⒉季终{(diào)整到合適的情況的方法只有將 header/ footer / cell 的子視圖跟其 safe area 關(guān)聯(lián)起來。
現(xiàn)在我們再試試把布局調(diào)整成更像 collection view 那樣:
截圖上可以看出來橫屏下, 左右兩邊的 cell 都被劉海擋住了延旧。這種情況下, 我們可以通過修改 section insets 來適配 safe area 來解決這個問題谋国。但是再 iOS 11 中, UICollectionViewFlowLayout 提供了一個新的屬性
sectionInsetReference
來幫你做這件事情迁沫。
@property (nonatomic) UICollectionViewFlowLayoutSectionInsetReference sectionInsetReference API_AVAILABLE(ios(11.0), tvos(11.0)) API_UNAVAILABLE(watchos);
typedef NS_ENUM(NSInteger, UICollectionViewFlowLayoutSectionInsetReference) {
UICollectionViewFlowLayoutSectionInsetFromContentInset,
UICollectionViewFlowLayoutSectionInsetFromSafeArea,
UICollectionViewFlowLayoutSectionInsetFromLayoutMargins
} API_AVAILABLE(ios(11.0), tvos(11.0)) API_UNAVAILABLE(watchos);
可以看出來芦瘾,系統(tǒng)默認(rèn)是使用 .fromContentInset 我們再分別修改, 看具體會是什么樣子的。
這種情況下 section content insets 等于原來的大小加上 safe area insets 的大小集畅。
跟使用 .fromLayoutMargins 相似使用這個屬性 colection view 的 layout margins 會被添加到 section content insets 上面近弟。
總結(jié)
1.在適配 iPhone X 的時候首先是要理解 safe area 是怎么回事。盲目的 if iPhoneX{} 只會給之后的工作代碼更多的麻煩挺智。
2.如果只需要適配到 iOS9 之前的 storyboard 都只需要做一件事情祷愉。
3.Xcode9 用 IB 可以看得出來, safe area 到處都是了。理解起來很簡單赦颇。就是系統(tǒng)對每個 View 都添加了 safe area二鳄, 這個區(qū)域的大小,是否跟 view 的大小相同是系統(tǒng)來決定的媒怯。在這個 View 上的布局只需要相對于 safe area 就可以了订讼。每個 View 的 safe area 都可以通過 iOS 11 新增的 API safeAreaInsets 或者 safeAreaLayoutGuide 獲取。
4.對與 UIViewController 來說新增了 additionalSafeAreaInsets 這個屬性, 用來管理有 tabbar 或者 navigation bar 的情況下額外的情況扇苞。
5.對于 UIScrollView躯嫉, UITableView, UICollectionView 這三個控件來說杨拐,系統(tǒng)以及做了大多數(shù)的事情。
scrollView 只需要設(shè)置 contentInsetAdjustmentBehavior 就可以很容易的適配帶 iPhoneX
tableView 只需要在 cell header footer 等設(shè)置約束的時候相對于 safe area 來做
對 collection view 來說修改 sectionInsetReference 為 .safeArea 就可以做大多數(shù)的事情了擂啥。
6.總的來說哄陶, safe area 可以看作是系統(tǒng)在所有的 view 上加了一個虛擬的 view, 這個虛擬的 view 的大小等都是跟 view 的位置等有關(guān)的(當(dāng)然是在 iPhoneX上才有值) 以后在寫代碼的時候哺壶,自定義的控件都盡量針對 safe area 這個虛擬的 view 進(jìn)行布局屋吨。