iOS11 适配

一、NavigationBar

1. UIBarItem

UIBarItem在iOS11在中新增landscapeImagePhone属性,用来在小图看不清楚的情况下,长按显示放大的图。在storyboard中也支持这个设置,对于HUD的image需要设置另一个iOS11新增的属性:largeContentSizeImage,关于这部分更详细的讨论,可以参考 WWDC2017 Session 215:What's New in Accessibility

2. 返回按钮的适配

iOS11之前移除返回按钮文字的代码:

[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60) forBarMetrics:UIBarMetricsDefault];
复制代码

iOS 11 中该方法依旧可以把文字移走,但是会往下偏一点,很不美观,并且编辑器会报出如下类似的警告

我的解决方法是将push方法包一层,自定义每个vc的navigationItem.backBarButtonItem,这种方式的时候,在childViewControllers使用push方法的时候会失效,需要使用当前viewController进行push操作

func dzy_push(_ vc:UIViewController, hide:Bool = false, animated:Bool = true) {
        if hide {
            vc.hidesBottomBarWhenPushed = hide
        }
        let btn = UIButton(type: .custom)
        btn.setImage(UIImage(named: "navi_back"), for: .normal)
        btn.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)
        btn.frame = CGRect(x: 0, y: 0, width: 25, height: 25)
        //隐藏返回按钮中的文字
        navigationItem.backBarButtonItem = UIBarButtonItem(customView: btn)
        navigationController?.pushViewController(vc, animated: animated)
    }
复制代码

可选择思路:

  • 自定义navigationController,重写push方法,在push方法中做类似的事情
  • viewController中做文章,重写返回按钮
LargeTitle

在iOS11导航栏多了一个LargeTitleView,默认是不开启的,专门显示大字标题用的,整个导航栏的高度达到了96p,这不包括状态栏的高度,也就是说,整个app顶部高度达到了116p,其中statusbar=20,title=44,largetitle=52,不过默认是64p;当然,iPhoneX的高度会更高点,如果不显示大字标题,顶部的高度也达到了88statusbar=44,title=44,如果显示大字标题,则高度变成了140statusbar=44,title=44,largetitle=52,也就是说,iPhoneX的刘海高度为24p,大字标题如下图:

具体设置: 在UI navigation bar中新增了一个BOOL属性prefersLargeTitles,将该属性设置为ture,navigation bar就会在整个APP中显示大标题,如果想要在控制不同页面大标题的显示,可以通过设置当前页面的navigationItemlargeTitleDisplayMode属性;

navigationItem.largeTitleDisplayMode 
typedef NS_ENUM(NSInteger, UINavigationItemLargeTitleDisplayMode) {  
/// 自动模式依赖上一个 item 的特性
UINavigationItemLargeTitleDisplayModeAutomatic,
/// 针对当前 item 总是启用大标题特性
UINavigationItemLargeTitleDisplayModeAlways,
/// Never 
UINavigationItemLargeTitleDisplayModeNever,
}
复制代码

3. Navigation 集成 UISearchController

把你的UISearchController赋值给navigationItem,就可以实现将UISearchController集成到NavigationBar

navigationItem.searchController  //iOS 11 新增属性
navigationItem.hidesSearchBarWhenScrolling //决定滑动的时候是否隐藏搜索框;iOS 11 新增属性
复制代码

4. 导航栏的边距变化

  1. 如果只是设置了titleView,没有设置barbutton,把titleview的宽度设置为屏幕宽度,则titleview距离屏幕的边距

    • iOS11之前,在iPhone6p上是20p,在iPhone6p之前是16p
    • iOS11之后,在iPhone6p上是12p,在iPhone6p之前是8p
  2. 如果只是设置了barbutton,没有设置titleview

    • 在iOS11里,barButton距离屏幕的边距是20p16p
    • 在iOS11之前,barButton距离屏幕的边距也是20p16p
  3. 如果同时设置了titleViewbarButton

    • 则在iOS11之前,titleview和barbutton之间的间距是6p
    • 在iOS11上titleview和barbutton之间无间距,如下图:

5. UINavigationController和滚动交互

滚动的时候,以下交互操作都是由UINavigationController负责调动的:

  1. UIsearchController搜索框效果更新
  2. 大标题效果的控制
  3. Rubber banding效果 //当你开始往下拉,大标题会变大来回应那个滚轮 所以,如果你使用navigation bar,组装一些整个push和pop体验,你不会得到searchController的集成、大标题的控制更新和Rubber banding效果,因为这些都是由UINavigationController控制的。

6. UIToolbar and UINavigationBar— Layout

在 iOS 11 中,当苹果进行所有这些新特性时,也进行了其他的优化,针对 UIToolbar 和 UINavigaBar 做了新的自动布局扩展支持,自定义的bar button items、自定义的title都可以通过layout来表示尺寸。 需要注意的是,你的constraints需要在view内部设置,所以如果你有一个自定义的标题视图,你需要确保任何约束只依赖于标题视图及其任何子视图。当你使用自动布局,系统假设你知道你在做什么。

7. Avoiding Zero-Sized Custom Views

自定义视图的size为0是因为你有一些模糊的约束布局。要避免视图尺寸为0,可以从以下方面做:

  • UINavigationBar 和 UIToolbar 提供位置
  • 开发者则必须提供视图的size,有三种方式:
    • 对宽度和高度的约束;
    • 实现 intrinsicContentSize;
    • 通过约束关联你的子视图;

二、UITabBar

iPhoneX不止多了刘海,底部还有一个半角的矩形,使得tabbar多出来了34p的高度,不过不管navigationBar还是tabbar一般系统都会自动适配safeArea。

三、管理margins 和 insets

1. layout margins

基于约束的Auto Layout,使我们搭建能够动态响应内部和外部变化的用户界面。Auto Layout为每一个view都定义了marginmargin指的是控件显示内容部分的边缘和控件边缘的距离。 可以用layoutMargins或者layoutMarginsGuide属性获得viewmargin,margin是视图内部的一部分。layoutMargins允许获取或者设置UIEdgeInsets结构的marginlayoutMarginsGuide则获取到只读的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));
复制代码

layoutMarginsUIEdgeInsets结构体类型的属性:

typedef struct UIEdgeInsets {  
CGFloat top, left, bottom, right;
} UIEdgeInsets;
复制代码

从上面两种结构体的对比可以看出,NSDirectionalEdgeInsets 属性用leadingtrailing 取代了之前的 leftrightdirectional 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的左边,可以通过layoutMarginleft属性读出该值。如下图所示:

还有其他一些更新。自从引入 layout margins,当将一个 view添加到 viewController时, viewController会修复 viewlayoutMarginsUIKit定义的一个值,这些调整对外是封闭的。从iOS11开始,这些不再是一个固定的值,它们实际是最小值,你可以改变 viewlayoutMargins为任意一个更大的值。而且, viewController新增了一个属性: viewRespectsSystemMinimumLayoutMargins,如果你设置该属性为 "false",你就可以改变你的 layoutMargins为任意你想设置的值,包括0,如下图所示:

2. 安全区域(Safe Area)

iOS 7 开始,在 UIViewController中引入的 topLayoutGuidebottomLayoutGuide 在 iOS 11 中被废弃了!取而代之的就是safeArea的概念,safeArea是描述你的视图部分不被任何内容遮挡的方法。 它提供两种方式:safeAreaInsetssafeAreaLayoutGuide来提供给你safeArea的参照值,即 insets 或者 layout guide。 safeArea区域如图所示:

如果有一个自定义的viewController,你可能要添加你自己的bars,增加safeAreaInsets的值,可以通过一个新的属性:addtionalSafeAreaInsets来改变safeAreaInsets的值,当你的viewController改变了它的safeAreaInsets值时,有两种方式获取到回调:

UIView.safeAreaInsetsDidChange()
UIViewController.viewSafeAreaInsetsDidChange()
复制代码

四、UIScrollView and UITableView的新特性

1. UIScrollView (弃用automaticallyAdjustsScrollViewInsets)

如果有一些文本位于UI滚动视图的内部,并包含在导航控制器中,一般navigationContollers会传入一个contentInset给其最顶层的viewController的scrollView,在iOS11中进行了一个很大的改变,不再通过scrollView的contentInset属性了,而是新增了一个属性:adjustedContentInset,通过下面两种图的对比,能够表示adjustContentInset表示的区域:

取消掉原有的 automaticallyAdjustsScrollViewInsets属性,新增的 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;
复制代码

针对automaticallyAdjustsScrollViewInsets属性被弃用,比较简单的适配方法

if (@available(iOS 11.0, *)) {
    self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
    self.automaticallyAdjustsScrollViewInsets = NO;
}
复制代码

2. Table Views :在iOS 11中默认启用Self-Sizing

这个应该是UITableView最大的改变。我们知道在iOS8引入Self-Sizing 之后,我们可以通过实现estimatedRowHeight相关的属性来展示动态的内容,实现了estimatedRowHeight属性后,得到的初始contenSize是个估算值,是通过estimatedRowHeight x cell的个数得到的,并不是最终的contenSizetableView不会一次性计算所有的cell的高度了,只会计算当前屏幕能够显示的cell个数再加上几个,滑动时,tableView不停地得到新的cell,更新自己的contenSize,在滑到最后的时候,会得到正确的contenSize。创建tableView到显示出来的过程中,contentSize的计算过程如下图:

Self-Sizing在iOS11下是默认开启的,Headers, footers, and cells都默认开启Self-Sizing,所有 estimated 高度默认值从iOS11之前的 0 改变为 UITableViewAutomaticDimension 由于Self-Sizing的缘故, contentSizecontentOffset的值在滚动的过程中会一直产生变化,所以如果目前的项目你还没有使用到Self-Sizing, contentSizecontentOffset有关的动画可能会有问题。

iOS 11中如果不实现-tableView: viewForFooterInSection:-tableView: viewForHeaderInSection:,那么-tableView: heightForHeaderInSection:- tableView: heightForFooterInSection:不会被调用。

iOS11下不想使用Self-Sizing的话,可以通过以下方式关闭:

self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;
复制代码

3. Table Views:separatorInset 扩展

iOS 7 引入separatorInset属性,用以设置 cell 的分割线边距,在 iOS 11 中对其进行了扩展。可以通过新增的UITableViewSeparatorInsetReference枚举类型的separatorInsetReference属性来设置separatorInset属性的参照值。

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

下图清晰的展示了这两种参照值的区别:

4. Table Views 和 Safe Area

有以下几点需要注意:

  1. separatorInset 被自动地关联到 safe area insets,因此,默认情况下,表视图的整个内容避免了其根视图控制器的安全区域的插入。
  2. UITableviewCellUITableViewHeaderFooterViewcontentview 在安全区域内;因此你应该始终在 contentview 中使用add-subviews操作。
  3. 所有的 headersfooters 都应该使用UITableViewHeaderFooterView,包括 table headersfooterssection headersfooters

5. 滑动操作(Swipe Actions)

在iOS8之后,苹果官方增加了UITableVIew的右滑操作接口,即新增了一个代理方法(tableView: editActionsForRowAtIndexPath:)和一个类(UITableViewRowAction),代理方法返回的是一个数组,我们可以在这个代理方法中定义所需要的操作按钮(删除、置顶等),这些按钮的类就是UITableViewRowAction。这个类只能定义按钮的显示文字、背景色、和按钮事件。并且返回数组的第一个元素在UITableViewCell的最右侧显示,最后一个元素在最左侧显示。从iOS 11开始有了一些改变,首先是可以给这些按钮添加图片了,然后是如果实现了以下两个iOS 11新增的代理方法,将会取代(tableView: editActionsForRowAtIndexPath:)代理方法:

// Swipe actions
// These methods supersede -editActionsForRowAtIndexPath: if implemented
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
复制代码

这两个代理方法返回的是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;
}
复制代码

创建UIContextualAction对象时,UIContextualActionStyle有两种类型,如果是置顶、已读等按钮就使用UIContextualActionStyleNormal类型,delete操作按钮可使用UIContextualActionStyleDestructive类型,当使用该类型时,如果是右滑操作,一直向右滑动某个cell,会直接执行删除操作,不用再点击删除按钮,这也是一个好玩的更新。

typedef NS_ENUM(NSInteger, UIContextualActionStyle) {
    UIContextualActionStyleNormal,
    UIContextualActionStyleDestructive
} NS_SWIFT_NAME(UIContextualAction.Style)
复制代码

滑动操作这里还有一个需要注意的是,当cell高度较小时,会只显示image,不显示title,当cell高度够大时,会同时显示imagetitle。我写demo测试的时候,因为每个cell的高度都较小,所以只显示image,然后我增加cell的高度后,就可以同时显示imagetitle了。见下图对比:

参考链接

你可能需要为你的APP适配iOS11

App界面适配iOS11(包括iPhoneX的奇葩尺寸)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值