iOS11及Xcode9适配问题汇总
UIScrollView and UITableView的新特性
ScrollView
如果有一些文本位于UI滚动视图的内部,并包含在导航控制器中,现在一般navigationContollers会传入一个contentInset
给其最顶层的viewController的scrollView,在iOS11中进行了一个很大的改变,不再通过scrollView的contentInset
属性了,而是新增了一个属性:adjustedContentInset
,通过下面两种图的对比,能够表示adjustContentInset
表示的区域:
新增的contentInsetAdjustmentBehavior
属性用来配置adjustedContentInset
的行为,该结构体有以下几种类型:
typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {
UIScrollViewContentInsetAdjustmentAutomatic,
UIScrollViewContentInsetAdjustmentScrollableAxes,
UIScrollViewContentInsetAdjustmentNever,
UIScrollViewContentInsetAdjustmentAlways,
}
@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior;
@property(nonatomic, readonly) UIEdgeInsets adjustedContentInset;
//adjustedContentInset值被改变的delegate
- (void)adjustedContentInsetDidChange;
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView;
UIScrollViewContentInsetAdjustmentBehavior 是一个枚举类型,值有以下几种:
- automatic 和scrollableAxes一样,scrollView会自动计算和适应顶部和底部的内边距并且在scrollView 不可滚动时,也会设置内边距.
- scrollableAxes 自动计算内边距.
- never不计算内边距
- always 根据safeAreaInsets 计算内边距
TableView
1.UITableview UICollectionView MJRefresh下拉刷新错乱或是莫名有20空隙的问题
if (@available(iOS 11.0, *)) {
_tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
_tableView.contentInset = UIEdgeInsetsMake(64, 0, 49, 0);//iPhoneX这里是88
_tableView.scrollIndicatorInsets = _tableView.contentInset;
}
如果改完后运行无效果 可以尝试Clean一下工程再运行, 很多小伙伴都遇到这样的问题了, 你不妨也试以试.
2.在iOS 11中默认启用Self-Sizing 未使用AutoLayout的TableView中的高度会出现问题.
Self-Sizing
在iOS11下是默认开启的,Headers, footers, and cells都默认开启Self-Sizing
,所有estimated
高度默认值从iOS11之前的 0
改变为UITableViewAutomaticDimension
.
如果目前项目中没有使用estimateRowHeight属性,在iOS11的环境下就要注意了,因为开启Self-Sizing
之后,tableView是使用estimateRowHeight
属性的,这样就会造成contentSize
和contentOffset
值的变化,如果是有动画是观察这两个属性的变化进行的,就会造成动画的异常,因为在估算行高机制下,contentSize
的值是一点点地变化更新的,所有cell显示完后才是最终的contentSize
值。因为不会缓存正确的行高,tableView reloadData
的时候,会重新计算contentSize
,就有可能会引起contentOffset
的变化。iOS11下不想使用Self-Sizing
的话,可以通过以下方式关闭:
self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;
3.TableView的separatorInset扩展
iOS 7 引入separatorInset
属性,用以设置 cell 的分割线边距,在 iOS 11 中对其进行了扩展。可以通过新增的UITableViewSeparatorInsetReference
枚举类型的separatorInsetReference
属性来设置separatorInset
属性的参照值.
通过下面的参考图可以看出他们的区别:
4. TableView和SafeArea(安全区)
有以下几点需要注意:
separatorInset
被自动地关联到 safe area insets,因此,默认情况下,表视图的整个内容避免了其根视图控制器的安全区域的插入。UITableviewCell
和UITableViewHeaderFooterView
的contentview
在安全区域内;因此你应该始终在contentview
中使用add-subviews
操作。- 所有的 headers 和 footers 都应该使用
UITableViewHeaderFooterView
,包括 table headers 和 footers、section headers 和 footers。
5. TableView的滑动操作
在iOS8之后,苹果官方增加了UITableVIew的右滑操作接口,即新增了一个代理方法tableView: editActionsForRowAtIndexPath:
和一个类UITableViewRowAction
,代理方法返回的是一个数组,我们可以在这个代理方法中定义所需要的操作按钮(删除、置顶等),这些按钮的类就是UITableViewRowAction
。这个类只能定义按钮的显示文字、背景色、和按钮事件。并且返回数组的第一个元素在UITableViewCell
的最右侧显示,最后一个元素在最左侧显示。从iOS 11开始有了一些改变,首先是可以给这些按钮添加图片了,然后是如果实现了以下两个iOS 11新增的代理方法,将会取代tableView: editActionsForRowAtIndexPath:
代理方法:
这两个代理方法返回的是UISwipeActionsConfiguration
类型的对象,创建该对象及赋值可看下面的代码片段:
- ( UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
//删除
UIContextualAction *deleteRowAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:@"delete" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
[self.titleArr removeObjectAtIndex:indexPath.row];
completionHandler (YES);
}];
deleteRowAction.image = [UIImage imageNamed:@"icon_del"];
deleteRowAction.backgroundColor = [UIColor blueColor];
UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteRowAction]];
return config;
}
typedef NS_ENUM(NSInteger, UIContextualActionStyle) {
UIContextualActionStyleNormal,
UIContextualActionStyleDestructive
} NS_SWIFT_NAME(UIContextualAction.Style)
创建UIContextualAction
对象时,UIContextualActionStyle
有两种类型,如果是置顶、已读等按钮就使用UIContextualActionStyleNormal
类型,delete操作按钮可使用UIContextualActionStyleDestructive
类型,当使用该类型时,如果是右滑操作,一直向右滑动某个cell,会直接执行删除操作,不用再点击删除按钮,这也是一个好玩的更新.
typedef NS_ENUM(NSInteger, UIContextualActionStyle) {
UIContextualActionStyleNormal,
UIContextualActionStyleDestructive
} NS_SWIFT_NAME(UIContextualAction.Style)
滑动操作这里还有一个需要注意的是,当cell高度较小时,会只显示image,不显示title,当cell高度够大时,会同时显示image和title。我写demo测试的时候,因为每个cell的高度都较小,所以只显示image,然后我增加cell的高度后,就可以同时显示image和title了。见下图对比:
iOS11中 UIKit’s Bars 上的变化
WWDC通过iOS新增的文件管理App:Files开始介绍,在Files这个APP中能够看到iOS11中UIKit’s Bars的一些新特性:在浏览功能上的大标题视图(向上滑动后标题会回到原来的UI效果)、横屏状态下tab上的文字和icon会变为左右排列:
在iPhone上,tab上的图标较小,tab bar较小,这样垂直空间可多放置内容。如果有人看不清楚tab bar上的图标或文字,可以通过长按tab bar上的任意item,会将该item显示在HUD上,这样可以清楚的看清icon和text。对tool bar 和 navigation bar同理,长按item也会放大显示.
- UIBarItem
UIBarItem是UI tab bar item和UI bar button item的父类,要想实现上面介绍的效果,只需要为UIBarItem 设置landscapeImagePhone
属性,在storyboard中也支持这个设置,对于HUD的image需要设置另一个iOS11新增的属性:largeContentSizeImage
,关于这部分更详细的讨论,可以参考 WWDC2017 Session 215:What's New in Accessibility
- 控制大标题的显示
在UINavigationbar
中新增了一个BOOL属性prefersLargeTitles
,将该属性设置为ture
,navigationbar就会在整个APP中显示大标题,如果想要在控制不同页面大标题的显示,可以通过设置当前页面的navigationItem
的largeTitleDisplayMode
属性.
navigationItem.largeTitleDisplayMode
typedef NS_ENUM(NSInteger, UINavigationItemLargeTitleDisplayMode) {
/// 自动模式依赖上一个 item 的特性
UINavigationItemLargeTitleDisplayModeAutomatic,
/// 针对当前 item 总是启用大标题特性
UINavigationItemLargeTitleDisplayModeAlways,
/// Never
UINavigationItemLargeTitleDisplayModeNever,
}
在 Navigation 集成 UISearchController
把你的UISearchController
赋值给navigationItem
,就可以实现将UISearchController
集成到 Navigation.
navigationItem.searchController //iOS 11 新增属性
navigationItem.hidesSearchBarWhenScrolling //决定滑动的时候是否隐藏搜索框;iOS 11 新增属性
UINavigationController和滚动交互
滚动的时候,以下交互操作都是由UINavigationController负责调动的:
UIsearchController搜索框效果更新
大标题效果的控制
Rubber banding效果 //当你开始往下拉,大标题会变大来回应那个滚轮
所以,如果你使用navigation bar,组装一些整体push和pop体验,你不会得到searchController的集成、大标题的控制更新和Rubber banding效果,因为这些都是由UINavigationController
控制的。
Margins 和 Insets
基于约束的Auto Layout, 使我们搭建能够动态响应内部和外部变化的用户界面. Auto Layout为每一个view
都定义了margin
. margin
指的是控件显示内容部分的边缘和控件边缘的距离.
可以用layoutMargins
或者layoutMarginsGuide
属性获得view
的margin
, margin
是视图内部的一部分. layoutMargins
允许获取或者设置UIEdgeInsets
结构的margin
. layoutMarginsGuide
则获取到只读的UILayoutGuide
对象.
在iOS11新增了一个属性:directional layout margins
,该属性是NSDirectionalEdgeInsets
结构体类型的属性:
typedef struct NSDirectionalEdgeInsets {
CGFloat top, leading, bottom, trailing;
} NSDirectionalEdgeInsets API_AVAILABLE(ios(11.0),tvos(11.0),watchos(4.0));
layoutMargins
是UIEdgeInsets
结构体类型的属性:
typedef struct UIEdgeInsets {
CGFloat top, left, bottom, right;
} UIEdgeInsets;
从上面两种结构体的对比可以看出, NSDirectionalEdgeInsets
属性用leading
和 trailing
取代了之前的 left
和 right
.
directional layout margins
属性的说明如下:
directionalLayoutMargins.leading is used on the left when the user interface direction is LTR and on the right for RTL.
Vice versa for directionalLayoutMargins.trailing.
例子: 当你设置了trailing = 30;
当在一个right to left 语言下trailing
的值会被设置在view
的左边, 可以通过layoutMargin
的left
属性读出该值. 如下图所示:
还有其他一些更新. 自从引入layout margins
, 当将一个view
添加到viewController
时, viewController
会修改view
的layoutMargins
为UIKit定义的一个值, 这些调整对外是封闭的. 从iOS11开始, 这些不再是一个固定的值, 它们实际是最小值, 你可以改变view
的layoutMargins
为任意一个更大的值. 而且, viewController
新增了一个属性: viewRespectsSystemMinimumLayoutMargins
, 如果你设置该属性为false
, 你就可以改变你的layoutMargins
为任意你想设置的值, 包括0
, 如下图所示: