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

一、Safe Area

从 iOS 7 开始,我们就在操作系统里提供这样的半透明的栏,并且鼓励你把要显示的内容布局延伸过这些栏,就像下图中照片 App 中做的那样。(注意顶部和底部都带有 Bar,且内容都被 Bar 所覆盖,产生出模糊效果)


Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第1张图片
image.png

之所以又这样的效果是利用了 UIViewController 的属性 edgesForExtendedLayout,它可以让作为 Container 的ViewControllers 定义这些(translucent bars)栏下View 的大小

edgesForExtendedLayout控制 View 的大小 让 translucent bars 覆盖其上之后带有模糊效果
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第2张图片
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第3张图片

默认情况下edgesForExtendedLayout 适用于所有的边缘,你可以通过topLayoutGuide 和 bottomLayoutGuide 两个属性来定义悬浮栏的大小。

Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第4张图片

从 iOS 11开始,系统将取消topLayoutGuide 和 bottomLayoutGuide属性,引入新的布局结构概念,SafeArea。

取消topLayoutGuide 和 bottomLayoutGuide属性 新的布局结构概念,SafeArea。
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第5张图片
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第6张图片

safeArea是描述你的视图部分不被任何内容遮挡的方法。 它提供两种方式:safeAreaInsets 或 safeAreaLayoutGuide 来提供给你 safeArea 的参照值,这两个属性定义在 UIView 中,它们分别对应 insets 或者 layout guide类型。

例如在你自定义的 ViewController 中添加一些自定义的栏样式 View,此时就需要改变 safeAreaInsets 的值。要想增加或减少safeAreaInsets的值,你可以通过调用 UIViewController 的新属性 additionalSafeAreaInsets (UIEdgeInsets 类型)在对应的位置增加 inset 值进而改变 safeAreaInsets。当你的viewController改变了它的safeAreaInsets值时,有两种方式获取到回调:

UIView.safeAreaInsetsDidChange()
UIViewController.viewSafeAreaInsetsDidChange()

每个 view 都可以改变 safeAreaInsets 的值,包括 UIViewController。

Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第7张图片
image.png

二、Scroll Views

下面例子中的结构是 UIVIewController + UIScrollView 包在
UINavigationController 里面。

Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第8张图片

以前如果一个 VIewController 中含有 ScrollView的话, 被
NavigationController 包住的这个 ViewController 会自动地调整 ScrollView 的 contentInset 值(增加64)如下

Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第9张图片

iOS 11之后这个行为已取消,取而代之的是,使用一个新的属性adjustedContentInset代替。而 contentInset 这个属性代表的概念简单明了,单单是内容的区域的 inset,不再与外界布局有关。

Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第10张图片

UIScrollView 支持自动布局,让scrollView可以根据所添加的sub-view的大小自动处理其可滚动区域的大小。iOS 11下更是添加了一些新的属性来协助开发中更快速的布局,其中包括 frameLayoutGuide 和 contentLayoutGuide 以及 contentInsetAdjustmentBehavior。

  1. frameLayoutGuide 负责scrollView在屏幕中的大小和位置,也就是你可以约束 scrollView 中的 sub-view 如下图中的 Page 1 labelView。当你滚动时,该 page 1 labelview 是固定不动的。
约束 scrollView 中的 sub-view Page 1 labelView 当内容滚动后,Page 1 位置不变
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第11张图片
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第12张图片
  1. contentLayoutGuide,你可以约束 sub-view 来控制器 scrollView 中可滚动区域的大小或者让内容随着滚动而移动。
指定 contentLayoutGuide 发生滚动时
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第13张图片
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第14张图片
  1. contentInsetAdjustmentBehavior属性用来配置adjustedContentInset的行为,该结构体有以下几种类型:
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));

当adjustedContentInset 值被改变后回调的代理方法有:

/* Also see -[UIScrollView adjustedContentInsetDidChange]
 */
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView API_AVAILABLE(ios(11.0), tvos(11.0));

三、Table Views

  1. 我们知道在iOS 8引入Self-Sizing 之后,可以通过实现estimatedRowHeight 相关的属性来展示动态的内容,当实现了estimatedRowHeight 属性后,tableview 会得到的初始 contenSize ,这是一个估算值,是通过estimatedRowHeight * cell的个数得到的,并不是最终的 contenSize。

因为有估算的 contentSize 值,所以tableView就不会一次性计算所有的cell的高度了,只会计算当前屏幕能够显示的cell个数再加上几个。

滑动时,tableView 不停地得到新的 cell,更新自己的 contenSize,在滑到最后的时候,会得到正确的 contenSize 。在测试Demo中,创建tableView 到显示出来的过程中,contentSize 的计算过程如下图:

Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第15张图片

  1. Self-Sizing
    在iOS 11中默认启用 Self-Sizing, 也就是说你 cell、header、footer对应的 estimated heights 默认值都从 iOS 11 之前的0 变为UITableViewAutomaticDimension。
    因为默认开启了 Self-Sizing,你在布局 cell 时需要确保内部子控件具备完整约束来让 tableview 自动计算出其需要的大小或者你在对应的 delegate 方法中返回每一个 cell 的真实高度值。同理也需要处理对应 header 和 footer 问题。

如果目前项目中没有使用estimateRowHeight属性,在iOS11的环境下就要注意了,因为开启Self-Sizing之后,tableView是使用estimateRowHeight属性的,这样就会造成contentSize和contentOffset值的变化,如果是有动画是观察这两个属性的变化进行的,就会造成动画的异常,因为在估算行高机制下,contentSize的值是一点点地变化更新的,所有cell显示完后才是最终的contentSize值。因为不会缓存正确的行高,tableView reloadData的时候,会重新计算contentSize,就有可能会引起contentOffset的变化。

如果你想 link 到 iOS 11 而不想使用这个默认开启的新特性(Self-Sizing)的话,你可以取消它,代码如下:

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

iOS11下,如果没有设置estimateRowHeight的值,也没有设置rowHeight的值,那contentSize计算初始值是 44 * cell的个数,如下图:rowHeight和estimateRowHeight都是默认值UITableViewAutomaticDimension 而rowNum = 15;则初始contentSize = 44 * 15 = 660;

Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第16张图片
  1. separatorInset
    tableView 的 readable content guide 概念,它是 View 内的一部分,也是内容布局的推荐区域。即使在大屏幕的 iPad 下,在 readable content guide 内布局的内容都能够获得不错的用户阅读体验。
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第17张图片

默认情况下 tableview 在 readable content guide 内有一个 separatorInset,它可以影响 cell 的默认分隔线位置 和 在 cell 内 labels 的位置。

separator.left = 0 separator.left = 30
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第18张图片
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第19张图片

可见 separatorInset 是对 readable content view 的 inset 处理。

iOS 11 之后,separatorInset 值影响的是,tableview 边框与屏幕的边缘的间隔大小,当设置左右为0时,效果如下

Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第20张图片
iPad 横屏下,separatorInset.left = 0 和 separatorInset.right = 0

如下是 separatorInset 值使用对别,其中在 iOS 11后添加可设置参照的属性UITableViewSeparatorInsetReference

typedef NS_ENUM(NSInteger, UITableViewSeparatorInsetReference) {  
UITableViewSeparatorInsetFromCellEdges,   //默认值,表示separatorInset是从cell的边缘的偏移量
UITableViewSeparatorInsetFromAutomaticInsets  //表示separatorInset属性值是从一个insets的偏移量
}

对比使用如下:

UITableViewSeparatorInsetFromCellEdges UITableViewSeparatorInsetFromAutomaticInsets
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第21张图片
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第22张图片
  1. tableview 与 Safe Area 交互需要注意几点:
  • separatorInset 被自动地关联到 safe area insets,因此,默认情况下,tabelview的整个内容区域避免了ViewController安全区域的插入。
  • UITableviewCell 和 UITableViewHeaderFooterView的 content view 在安全区域内;因此你应该始终在 content view 中使用add-subviews操作
  • 你应该使用带有 content view 的 UITableViewHeaderFooterView类实例作为table headers 和 footers、section headers 和 footers。
  1. Swipe Actions
    1. 新的滚动条:带有 time stamps 时码的滚动条
    2. 实现 full swipe-to-delete 功能
    3. 添加了又滑功能

测试默认开启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 ()

@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 = @"出席统计";
}

- (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 无效
没有调用viewForFooterInSection和viewForHeaderInSection运行正常 没有调用viewForFooterInSection和viewForHeaderInSection运行有留白 调用viewForFooterInSection和viewForHeaderInSection运行正常
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第23张图片
iOS10NOReturnViewFooterHeader.gif
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第24张图片
NOReturnHeaderOrFooterViewQuestion.gif
Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性)_第25张图片
wihtReturnHeaderOrFooterView.gif

另一宗办法就是,关闭 iOS 11默认打开的 Self-Sizing 功能

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

问题1测试 demo

你可能感兴趣的:(Updating Your App for iOS 11 - Part2(Safe Area/ScrollView/TableView的新特性))