一甫题、Safe Area
從 iOS 7 開始派敷,我們就在操作系統(tǒng)里提供這樣的半透明的欄,并且鼓勵你把要顯示的內(nèi)容布局延伸過這些欄炕吸,就像下圖中照片 App 中做的那樣。(注意頂部和底部都帶有 Bar勉痴,且內(nèi)容都被 Bar 所覆蓋赫模,產(chǎn)生出模糊效果)
之所以又這樣的效果是利用了 UIViewController 的屬性 edgesForExtendedLayout,它可以讓作為 Container 的ViewControllers 定義這些(translucent bars)欄下View 的大小
edgesForExtendedLayout控制 View 的大小 | 讓 translucent bars 覆蓋其上之后帶有模糊效果 |
---|---|
默認(rèn)情況下edgesForExtendedLayout 適用于所有的邊緣蒸矛,你可以通過topLayoutGuide 和 bottomLayoutGuide 兩個屬性來定義懸浮欄的大小瀑罗。
從 iOS 11開始,系統(tǒng)將取消topLayoutGuide 和 bottomLayoutGuide屬性雏掠,引入新的布局結(jié)構(gòu)概念斩祭,SafeArea。
取消topLayoutGuide 和 bottomLayoutGuide屬性 | 新的布局結(jié)構(gòu)概念乡话,SafeArea摧玫。 |
---|---|
safeArea是描述你的視圖部分不被任何內(nèi)容遮擋的方法。 它提供兩種方式:safeAreaInsets 或 safeAreaLayoutGuide 來提供給你 safeArea 的參照值绑青,這兩個屬性定義在 UIView 中诬像,它們分別對應(yīng) insets 或者 layout guide類型。
例如在你自定義的 ViewController 中添加一些自定義的欄樣式 View闸婴,此時就需要改變 safeAreaInsets 的值坏挠。要想增加或減少safeAreaInsets的值,你可以通過調(diào)用 UIViewController 的新屬性 additionalSafeAreaInsets (UIEdgeInsets 類型)在對應(yīng)的位置增加 inset 值進(jìn)而改變 safeAreaInsets邪乍。當(dāng)你的viewController改變了它的safeAreaInsets值時降狠,有兩種方式獲取到回調(diào):
UIView.safeAreaInsetsDidChange()
UIViewController.viewSafeAreaInsetsDidChange()
每個 view 都可以改變 safeAreaInsets 的值对竣,包括 UIViewController。
二榜配、Scroll Views
下面例子中的結(jié)構(gòu)是 UIVIewController + UIScrollView 包在
UINavigationController 里面否纬。
以前如果一個 VIewController 中含有 ScrollView的話, 被
NavigationController 包住的這個 ViewController 會自動地調(diào)整 ScrollView 的 contentInset 值(增加64)如下
iOS 11之后這個行為已取消蛋褥,取而代之的是烦味,使用一個新的屬性adjustedContentInset代替。而 contentInset 這個屬性代表的概念簡單明了壁拉,單單是內(nèi)容的區(qū)域的 inset,不再與外界布局有關(guān)柏靶。
UIScrollView 支持自動布局弃理,讓scrollView可以根據(jù)所添加的sub-view的大小自動處理其可滾動區(qū)域的大小。iOS 11下更是添加了一些新的屬性來協(xié)助開發(fā)中更快速的布局屎蜓,其中包括 frameLayoutGuide 和 contentLayoutGuide 以及 contentInsetAdjustmentBehavior痘昌。
- frameLayoutGuide 負(fù)責(zé)scrollView在屏幕中的大小和位置,也就是你可以約束 scrollView 中的 sub-view 如下圖中的 Page 1 labelView炬转。當(dāng)你滾動時辆苔,該 page 1 labelview 是固定不動的。
約束 scrollView 中的 sub-view Page 1 labelView | 當(dāng)內(nèi)容滾動后扼劈,Page 1 位置不變 |
---|---|
- contentLayoutGuide驻啤,你可以約束 sub-view 來控制器 scrollView 中可滾動區(qū)域的大小或者讓內(nèi)容隨著滾動而移動。
指定 contentLayoutGuide | 發(fā)生滾動時 |
---|---|
- contentInsetAdjustmentBehavior屬性用來配置adjustedContentInset的行為荐吵,該結(jié)構(gòu)體有以下幾種類型:
typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {
UIScrollViewContentInsetAdjustmentAutomatic, // Similar to .scrollableAxes, but for backward compatibility will also adjust the top & bottom contentInset when the scroll view is owned by a view controller with automaticallyAdjustsScrollViewInsets = YES inside a navigation controller, regardless of whether the scroll view is scrollable
UIScrollViewContentInsetAdjustmentScrollableAxes, // Edges for scrollable axes are adjusted (i.e., contentSize.width/height > frame.size.width/height or alwaysBounceHorizontal/Vertical = YES)
UIScrollViewContentInsetAdjustmentNever, // contentInset is not adjusted
UIScrollViewContentInsetAdjustmentAlways, // contentInset is always adjusted by the scroll view's safeAreaInsets
} API_AVAILABLE(ios(11.0),tvos(11.0));
/* Configure the behavior of adjustedContentInset.
Default is UIScrollViewContentInsetAdjustmentAutomatic.
*/
@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior API_AVAILABLE(ios(11.0),tvos(11.0));
/* When contentInsetAdjustmentBehavior allows, UIScrollView may incorporate
its safeAreaInsets into the adjustedContentInset.
*/
@property(nonatomic, readonly) UIEdgeInsets adjustedContentInset API_AVAILABLE(ios(11.0),tvos(11.0));
當(dāng)adjustedContentInset 值被改變后回調(diào)的代理方法有:
/* Also see -[UIScrollView adjustedContentInsetDidChange]
*/
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView API_AVAILABLE(ios(11.0), tvos(11.0));
三骑冗、Table Views
- 我們知道在iOS 8引入Self-Sizing 之后,可以通過實現(xiàn)estimatedRowHeight 相關(guān)的屬性來展示動態(tài)的內(nèi)容先煎,當(dāng)實現(xiàn)了estimatedRowHeight 屬性后贼涩,tableview 會得到的初始 contenSize ,這是一個估算值薯蝎,是通過estimatedRowHeight * cell的個數(shù)得到的遥倦,并不是最終的 contenSize。
因為有估算的 contentSize 值占锯,所以tableView就不會一次性計算所有的cell的高度了袒哥,只會計算當(dāng)前屏幕能夠顯示的cell個數(shù)再加上幾個。
滑動時烟央,tableView 不停地得到新的 cell统诺,更新自己的 contenSize,在滑到最后的時候疑俭,會得到正確的 contenSize 粮呢。在測試Demo中,創(chuàng)建tableView 到顯示出來的過程中,contentSize 的計算過程如下圖:
- Self-Sizing
在iOS 11中默認(rèn)啟用 Self-Sizing, 也就是說你 cell啄寡、header豪硅、footer對應(yīng)的 estimated heights 默認(rèn)值都從 iOS 11 之前的0 變?yōu)閁ITableViewAutomaticDimension。
因為默認(rèn)開啟了 Self-Sizing挺物,你在布局 cell 時需要確保內(nèi)部子控件具備完整約束來讓 tableview 自動計算出其需要的大小或者你在對應(yīng)的 delegate 方法中返回每一個 cell 的真實高度值懒浮。同理也需要處理對應(yīng) header 和 footer 問題。
如果目前項目中沒有使用estimateRowHeight屬性识藤,在iOS11的環(huán)境下就要注意了砚著,因為開啟Self-Sizing之后,tableView是使用estimateRowHeight屬性的痴昧,這樣就會造成contentSize和contentOffset值的變化稽穆,如果是有動畫是觀察這兩個屬性的變化進(jìn)行的,就會造成動畫的異常赶撰,因為在估算行高機制下舌镶,contentSize的值是一點點地變化更新的,所有cell顯示完后才是最終的contentSize值豪娜。因為不會緩存正確的行高餐胀,tableView reloadData的時候,會重新計算contentSize瘤载,就有可能會引起contentOffset的變化否灾。
如果你想 link 到 iOS 11 而不想使用這個默認(rèn)開啟的新特性(Self-Sizing)的話,你可以取消它鸣奔,代碼如下:
override func viewDidLoad() {
//取消 estimated sizes 功能和 tableview 的 Self-Sizing 功能
tableView.estimatedRowHeight = 0
tableView.estimatedSectionHeaderHeight = 0
tableView.estimatedSectionFooterHeight = 0
}
iOS11下坟冲,如果沒有設(shè)置estimateRowHeight的值,也沒有設(shè)置rowHeight的值溃蔫,那contentSize計算初始值是 44 * cell的個數(shù)健提,如下圖:rowHeight和estimateRowHeight都是默認(rèn)值UITableViewAutomaticDimension 而rowNum = 15;則初始contentSize = 44 * 15 = 660伟叛;
- separatorInset
tableView 的 readable content guide 概念私痹,它是 View 內(nèi)的一部分,也是內(nèi)容布局的推薦區(qū)域统刮。即使在大屏幕的 iPad 下紊遵,在 readable content guide 內(nèi)布局的內(nèi)容都能夠獲得不錯的用戶閱讀體驗。
默認(rèn)情況下 tableview 在 readable content guide 內(nèi)有一個 separatorInset侥蒙,它可以影響 cell 的默認(rèn)分隔線位置 和 在 cell 內(nèi) labels 的位置暗膜。
separator.left = 0 | separator.left = 30 |
---|---|
可見 separatorInset 是對 readable content view 的 inset 處理。
iOS 11 之后鞭衩,separatorInset 值影響的是学搜,tableview 邊框與屏幕的邊緣的間隔大小娃善,當(dāng)設(shè)置左右為0時,效果如下
如下是 separatorInset 值使用對別聚磺,其中在 iOS 11后添加可設(shè)置參照的屬性UITableViewSeparatorInsetReference
typedef NS_ENUM(NSInteger, UITableViewSeparatorInsetReference) {
UITableViewSeparatorInsetFromCellEdges, //默認(rèn)值,表示separatorInset是從cell的邊緣的偏移量
UITableViewSeparatorInsetFromAutomaticInsets //表示separatorInset屬性值是從一個insets的偏移量
}
對比使用如下:
UITableViewSeparatorInsetFromCellEdges | UITableViewSeparatorInsetFromAutomaticInsets |
---|---|
- tableview 與 Safe Area 交互需要注意幾點:
- separatorInset 被自動地關(guān)聯(lián)到 safe area insets炬丸,因此瘫寝,默認(rèn)情況下,tabelview的整個內(nèi)容區(qū)域避免了ViewController安全區(qū)域的插入稠炬。
- UITableviewCell 和 UITableViewHeaderFooterView的 content view 在安全區(qū)域內(nèi)焕阿;因此你應(yīng)該始終在 content view 中使用add-subviews操作
- 你應(yīng)該使用帶有 content view 的 UITableViewHeaderFooterView類實例作為table headers 和 footers、section headers 和 footers首启。
- Swipe Actions
- 新的滾動條:帶有 time stamps 時碼的滾動條
- 實現(xiàn) full swipe-to-delete 功能
- 添加了又滑功能
測試默認(rèn)開啟Self-Sizing的 iOS 11問題捣鲸。
問題1:如下代碼是運行在 iOS 10下正常,但運行在 iOS 11則在 tabelView 上下有留白問題
//
// ViewController.m
// ios10TabelView
//
// Created by Jacob_Liang on 2017/9/21.
// Copyright ? 2017年 Jacob. All rights reserved.
//
#import "ViewController.h"
static NSString * const CELLID = @"CELLID";
@interface ViewController ()<UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, weak) UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setUpInit];
[self setUpNav];
[self setUpTableView];
}
- (void)setUpInit {
self.automaticallyAdjustsScrollViewInsets = NO; //iOS 11下被廢棄了闽坡,寫了也沒用
self.view.backgroundColor = [UIColor purpleColor];
}
- (void)setUpNav {
self.navigationItem.title = @"出席統(tǒng)計";
}
- (void)setUpTableView {
CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
CGFloat screenH = [UIScreen mainScreen].bounds.size.height;
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, screenW, screenH - 64) style:UITableViewStyleGrouped];
[self.view addSubview:tableView];
_tableView = tableView;
tableView.backgroundColor = [UIColor lightGrayColor];
tableView.delegate = self;
tableView.dataSource = self;
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CELLID];
}
#pragma mark - UITableViewDelegate & UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 15;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CELLID forIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:@"%@",indexPath];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 50;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 0.01;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.01;
}
@end
上述代碼運行情況,注意此時 tableview 的 Style 為 UITableViewStyleGrouped
在 iOS 10 下 | iOS 11下 | iOS 11下 |
---|---|---|
self.automaticallyAdjustsScrollViewInsets = NO 有效 | self.automaticallyAdjustsScrollViewInsets = NO 無效 | self.automaticallyAdjustsScrollViewInsets = NO 無效 |
沒有調(diào)用viewForFooterInSection和viewForHeaderInSection運行正常 | 沒有調(diào)用viewForFooterInSection和viewForHeaderInSection運行有留白 | 調(diào)用viewForFooterInSection和viewForHeaderInSection運行正常 |
iOS10NOReturnViewFooterHeader.gif
|
NOReturnHeaderOrFooterViewQuestion.gif
|
wihtReturnHeaderOrFooterView.gif
|
另一宗辦法就是愁溜,關(guān)閉 iOS 11默認(rèn)打開的 Self-Sizing 功能
tableView.estimatedRowHeight = 0;
tableView.estimatedSectionFooterHeight = 0;
tableView.estimatedSectionHeaderHeight = 0;