Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)

一、Safe Area

從 iOS 7 開始,我們就在操作系統(tǒng)里提供這樣的半透明的欄,并且鼓勵(lì)你把要顯示的內(nèi)容布局延伸過(guò)這些欄,就像下圖中照片 App 中做的那樣。(注意頂部和底部都帶有 Bar,且內(nèi)容都被 Bar 所覆蓋,產(chǎn)生出模糊效果)


image.png

之所以又這樣的效果是利用了 UIViewController 的屬性 edgesForExtendedLayout,它可以讓作為 Container 的ViewControllers 定義這些(translucent bars)欄下View 的大小

edgesForExtendedLayout控制 View 的大小 讓 translucent bars 覆蓋其上之后帶有模糊效果

默認(rèn)情況下edgesForExtendedLayout 適用于所有的邊緣,你可以通過(guò)topLayoutGuide 和 bottomLayoutGuide 兩個(gè)屬性來(lái)定義懸浮欄的大小。

從 iOS 11開始,系統(tǒng)將取消topLayoutGuide 和 bottomLayoutGuide屬性,引入新的布局結(jié)構(gòu)概念,SafeArea。

取消topLayoutGuide 和 bottomLayoutGuide屬性 新的布局結(jié)構(gòu)概念,SafeArea。

safeArea是描述你的視圖部分不被任何內(nèi)容遮擋的方法。 它提供兩種方式:safeAreaInsets 或 safeAreaLayoutGuide 來(lái)提供給你 safeArea 的參照值,這兩個(gè)屬性定義在 UIView 中,它們分別對(duì)應(yīng) insets 或者 layout guide類型。

例如在你自定義的 ViewController 中添加一些自定義的欄樣式 View,此時(shí)就需要改變 safeAreaInsets 的值。要想增加或減少safeAreaInsets的值,你可以通過(guò)調(diào)用 UIViewController 的新屬性 additionalSafeAreaInsets (UIEdgeInsets 類型)在對(duì)應(yīng)的位置增加 inset 值進(jìn)而改變 safeAreaInsets。當(dāng)你的viewController改變了它的safeAreaInsets值時(shí),有兩種方式獲取到回調(diào):

UIView.safeAreaInsetsDidChange()
UIViewController.viewSafeAreaInsetsDidChange()

每個(gè) view 都可以改變 safeAreaInsets 的值,包括 UIViewController。

image.png

二、Scroll Views

下面例子中的結(jié)構(gòu)是 UIVIewController + UIScrollView 包在
UINavigationController 里面。

以前如果一個(gè) VIewController 中含有 ScrollView的話, 被
NavigationController 包住的這個(gè) ViewController 會(huì)自動(dòng)地調(diào)整 ScrollView 的 contentInset 值(增加64)如下

iOS 11之后這個(gè)行為已取消,取而代之的是,使用一個(gè)新的屬性adjustedContentInset代替。而 contentInset 這個(gè)屬性代表的概念簡(jiǎn)單明了,單單是內(nèi)容的區(qū)域的 inset,不再與外界布局有關(guān)。

UIScrollView 支持自動(dòng)布局,讓scrollView可以根據(jù)所添加的sub-view的大小自動(dòng)處理其可滾動(dòng)區(qū)域的大小。iOS 11下更是添加了一些新的屬性來(lái)協(xié)助開發(fā)中更快速的布局,其中包括 frameLayoutGuide 和 contentLayoutGuide 以及 contentInsetAdjustmentBehavior。

  1. frameLayoutGuide 負(fù)責(zé)scrollView在屏幕中的大小和位置,也就是你可以約束 scrollView 中的 sub-view 如下圖中的 Page 1 labelView。當(dāng)你滾動(dòng)時(shí),該 page 1 labelview 是固定不動(dòng)的。
約束 scrollView 中的 sub-view Page 1 labelView 當(dāng)內(nèi)容滾動(dòng)后,Page 1 位置不變
  1. contentLayoutGuide,你可以約束 sub-view 來(lái)控制器 scrollView 中可滾動(dòng)區(qū)域的大小或者讓內(nèi)容隨著滾動(dòng)而移動(dòng)。
指定 contentLayoutGuide 發(fā)生滾動(dòng)時(shí)
  1. contentInsetAdjustmentBehavior屬性用來(lái)配置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

  1. 我們知道在iOS 8引入Self-Sizing 之后,可以通過(guò)實(shí)現(xiàn)estimatedRowHeight 相關(guān)的屬性來(lái)展示動(dòng)態(tài)的內(nèi)容,當(dāng)實(shí)現(xiàn)了estimatedRowHeight 屬性后,tableview 會(huì)得到的初始 contenSize ,這是一個(gè)估算值,是通過(guò)estimatedRowHeight * cell的個(gè)數(shù)得到的,并不是最終的 contenSize。

因?yàn)橛泄浪愕?contentSize 值,所以tableView就不會(huì)一次性計(jì)算所有的cell的高度了,只會(huì)計(jì)算當(dāng)前屏幕能夠顯示的cell個(gè)數(shù)再加上幾個(gè)。

滑動(dòng)時(shí),tableView 不停地得到新的 cell,更新自己的 contenSize,在滑到最后的時(shí)候,會(huì)得到正確的 contenSize 。在測(cè)試Demo中,創(chuàng)建tableView 到顯示出來(lái)的過(guò)程中,contentSize 的計(jì)算過(guò)程如下圖:

  1. Self-Sizing
    在iOS 11中默認(rèn)啟用 Self-Sizing, 也就是說(shuō)你 cell、header、footer對(duì)應(yīng)的 estimated heights 默認(rèn)值都從 iOS 11 之前的0 變?yōu)閁ITableViewAutomaticDimension。
    因?yàn)槟J(rèn)開啟了 Self-Sizing,你在布局 cell 時(shí)需要確保內(nèi)部子控件具備完整約束來(lái)讓 tableview 自動(dòng)計(jì)算出其需要的大小或者你在對(duì)應(yīng)的 delegate 方法中返回每一個(gè) cell 的真實(shí)高度值。同理也需要處理對(duì)應(yīng) header 和 footer 問(wèn)題。

如果目前項(xiàng)目中沒(méi)有使用estimateRowHeight屬性,在iOS11的環(huán)境下就要注意了,因?yàn)殚_啟Self-Sizing之后,tableView是使用estimateRowHeight屬性的,這樣就會(huì)造成contentSize和contentOffset值的變化,如果是有動(dòng)畫是觀察這兩個(gè)屬性的變化進(jìn)行的,就會(huì)造成動(dòng)畫的異常,因?yàn)樵诠浪阈懈邫C(jī)制下,contentSize的值是一點(diǎn)點(diǎn)地變化更新的,所有cell顯示完后才是最終的contentSize值。因?yàn)椴粫?huì)緩存正確的行高,tableView reloadData的時(shí)候,會(huì)重新計(jì)算contentSize,就有可能會(huì)引起contentOffset的變化。

如果你想 link 到 iOS 11 而不想使用這個(gè)默認(rèn)開啟的新特性(Self-Sizing)的話,你可以取消它,代碼如下:

override func viewDidLoad() {
//取消 estimated sizes 功能和 tableview 的 Self-Sizing 功能
 tableView.estimatedRowHeight = 0
 tableView.estimatedSectionHeaderHeight = 0
 tableView.estimatedSectionFooterHeight = 0
}

iOS11下,如果沒(méi)有設(shè)置estimateRowHeight的值,也沒(méi)有設(shè)置rowHeight的值,那contentSize計(jì)算初始值是 44 * cell的個(gè)數(shù),如下圖:rowHeight和estimateRowHeight都是默認(rèn)值UITableViewAutomaticDimension 而rowNum = 15;則初始contentSize = 44 * 15 = 660;

  1. separatorInset
    tableView 的 readable content guide 概念,它是 View 內(nèi)的一部分,也是內(nèi)容布局的推薦區(qū)域。即使在大屏幕的 iPad 下,在 readable content guide 內(nèi)布局的內(nèi)容都能夠獲得不錯(cuò)的用戶閱讀體驗(yàn)。

默認(rèn)情況下 tableview 在 readable content guide 內(nèi)有一個(gè) separatorInset,它可以影響 cell 的默認(rèn)分隔線位置 和 在 cell 內(nèi) labels 的位置。

separator.left = 0 separator.left = 30

可見(jiàn) separatorInset 是對(duì) readable content view 的 inset 處理。

iOS 11 之后,separatorInset 值影響的是,tableview 邊框與屏幕的邊緣的間隔大小,當(dāng)設(shè)置左右為0時(shí),效果如下

iPad 橫屏下,separatorInset.left = 0 和 separatorInset.right = 0

如下是 separatorInset 值使用對(duì)別,其中在 iOS 11后添加可設(shè)置參照的屬性UITableViewSeparatorInsetReference

typedef NS_ENUM(NSInteger, UITableViewSeparatorInsetReference) {  
UITableViewSeparatorInsetFromCellEdges,   //默認(rèn)值,表示separatorInset是從cell的邊緣的偏移量
UITableViewSeparatorInsetFromAutomaticInsets  //表示separatorInset屬性值是從一個(gè)insets的偏移量
}

對(duì)比使用如下:

UITableViewSeparatorInsetFromCellEdges UITableViewSeparatorInsetFromAutomaticInsets
  1. tableview 與 Safe Area 交互需要注意幾點(diǎn):
  • separatorInset 被自動(dòng)地關(guān)聯(lián)到 safe area insets,因此,默認(rèn)情況下,tabelview的整個(gè)內(nèi)容區(qū)域避免了ViewController安全區(qū)域的插入。
  • UITableviewCell 和 UITableViewHeaderFooterView的 content view 在安全區(qū)域內(nèi);因此你應(yīng)該始終在 content view 中使用add-subviews操作
  • 你應(yīng)該使用帶有 content view 的 UITableViewHeaderFooterView類實(shí)例作為table headers 和 footers、section headers 和 footers。
  1. Swipe Actions
    1. 新的滾動(dòng)條:帶有 time stamps 時(shí)碼的滾動(dòng)條
    2. 實(shí)現(xiàn) full swipe-to-delete 功能
    3. 添加了又滑功能

測(cè)試默認(rèn)開啟Self-Sizing的 iOS 11問(wèn)題。

問(wèn)題1:如下代碼是運(yùn)行在 iOS 10下正常,但運(yùn)行在 iOS 11則在 tabelView 上下有留白問(wèn)題
//
//  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下被廢棄了,寫了也沒(méi)用
    self.view.backgroundColor = [UIColor purpleColor];
}

- (void)setUpNav {
    self.navigationItem.title = @"出席統(tǒng)計(jì)";
}

- (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

上述代碼運(yùn)行情況,注意此時(shí) tableview 的 Style 為 UITableViewStyleGrouped

在 iOS 10 下 iOS 11下 iOS 11下
self.automaticallyAdjustsScrollViewInsets = NO 有效 self.automaticallyAdjustsScrollViewInsets = NO 無(wú)效 self.automaticallyAdjustsScrollViewInsets = NO 無(wú)效
沒(méi)有調(diào)用viewForFooterInSection和viewForHeaderInSection運(yùn)行正常 沒(méi)有調(diào)用viewForFooterInSection和viewForHeaderInSection運(yùn)行有留白 調(diào)用viewForFooterInSection和viewForHeaderInSection運(yùn)行正常
iOS10NOReturnViewFooterHeader.gif
NOReturnHeaderOrFooterViewQuestion.gif
wihtReturnHeaderOrFooterView.gif

另一宗辦法就是,關(guān)閉 iOS 11默認(rèn)打開的 Self-Sizing 功能

    tableView.estimatedRowHeight = 0;
    tableView.estimatedSectionFooterHeight = 0;
    tableView.estimatedSectionHeaderHeight = 0;

問(wèn)題1測(cè)試 demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS 11 為整個(gè)生態(tài)系統(tǒng)的 UI 元素帶來(lái)了一種更加大膽、動(dòng)態(tài)的新風(fēng)格。 本文介紹iOS11中在UI方面做了哪...
    阿凡提說(shuō)AI閱讀 641評(píng)論 0 1
  • 版權(quán)聲明:未經(jīng)本人允許,禁止轉(zhuǎn)載. 1. TableView初始化 1.UITableView有兩種風(fēng)格:UITa...
    蕭雪痕閱讀 2,992評(píng)論 2 10
  • 我們?cè)谏弦黄锻ㄟ^(guò)代碼自定義不等高cell》中學(xué)習(xí)了tableView的相關(guān)知識(shí),本文將在上文的基礎(chǔ)上,利用sto...
    啊世ka閱讀 1,658評(píng)論 2 7
  • 人心里的悲觀,只不過(guò)是他見(jiàn)到了陰暗給眼前的坎,心生恐懼;又遭遇了黑暗給人生存的艱難,意志消沉。 ...
    汩月閱讀 738評(píng)論 0 0
  • 下午放學(xué)時(shí)候,女兒得意的告訴我,數(shù)學(xué)考試得了110分,滿分。緊接著愁眉苦臉說(shuō),可是明天她們要考英語(yǔ),她的英語(yǔ)總是考...
    彭曉芬閱讀 280評(píng)論 0 3

友情鏈接更多精彩內(nèi)容