iOS 11 & iPhoneX 适配

一、safeArea

  1. automaticallyAdjustsScrollViewInsets to contentInsetAdjustmentBehavior
    在iOS 11中,苹果废弃了 UIViewControllerautomaticallyAdjustsScrollViewInsets属性,改用UIScrollViewcontentInsetAdjustmentBehavior 属性来替换它。
// OC
@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets API_DEPRECATED_WITH_REPLACEMENT("Use UIScrollView's contentInsetAdjustmentBehavior instead", ios(7.0,11.0),tvos(7.0,11.0)); // Defaults to YES

//swift
@available(iOS, introduced: 7.0, deprecated: 11.0)
open var automaticallyAdjustsScrollViewInsets: Bool // Defaults to YES

如果你的工程中没有添加 iPhone X 对应的启动图,你会发现顶部和底部的一部分都是黑色的,那部分就是安全域。
想要对 iPhone X 做屏幕适配,第一步先要添加启动图。

image.png

二、UITableView 自动计算高度

在iOS 11中,UITableViewestimatedRowHeight
estimatedSectionHeaderHeight
estimatedSectionFooterHeight 默认都是开启的。真是令人惊喜。

@property (nonatomic) CGFloat rowHeight;             // default is UITableViewAutomaticDimension
@property (nonatomic) CGFloat sectionHeaderHeight;   // default is UITableViewAutomaticDimension
@property (nonatomic) CGFloat sectionFooterHeight;   // default is UITableViewAutomaticDimension
@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable
@property (nonatomic) CGFloat estimatedSectionHeaderHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable
@property (nonatomic) CGFloat estimatedSectionFooterHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable

踩坑1

在我们的app中,首页的 UITableView 列表是手动计算的行高,突然出现了以往只有开启自动计算行高才会出现的滚动条跳动的现象,给 TableViewcontentSize 加了observer,果然一直在变。然后在这里找到了原因。

踩坑2 (更新)

在使用 MJRefersh 的时候,本身代码存在一个 bug,原来并没有发现,iOS 11 帮我发现了这个问题。

// tableView 设置
let footer = MJRefreshAutoFooter(refreshingBlock: {  [weak self] in
    if let strongSelf = self {
        if let block = strongSelf.footerBlock {
            block()
        }
    }
})
footer?.triggerAutomaticallyRefreshPercent = -20
tableView.mj_footer = footer

// 请求结束后处理(错误)
models.append(requestModels)
if models.count < pageSize { // 这里是个bug
    tableView.mj_footer.endRefreshingWithNoMoreData()
} else {
    tableView.mj_footer.state = .idle
}
tableView.reloadData()

// 请求结束后处理(正确)
if requestModels.count < pageSize { // 修复bug
    tableView.mj_footer.endRefreshingWithNoMoreData()
} else {
    tableView.mj_footer.state = .idle
}
models.append(requestModels)
tableView.reloadData()

上面代码中,在 iOS 11 之前也存在隐含的问题,就是 tableView footer 一直不能被标记为 无更多数据 的状态,即使没有数据了,用户下拉依然后发起请求。
iOS 11 中,由于估算行高,互动到底部时,依然会触发 tableViewcontentSizecontentOffset 变化,同时会触发 refreshingBlock, 和 tableViewreloadData,导致了最后一页数据陷入了循环请求。

引申问题
如果存在使用监听 tableViewcontentSizecontentOffset 变化来触发的一些事件,最好把 tableView 的自动计算行高关掉,以免出现问题。

三、iPhone X(严重)

1. 状态栏获取网络状态

众所周知,iPhone X多了个“美美的”刘海,它的状态栏发生了变化。这也导致了原来从状态栏视图获取网络状态的API出了问题,会导致闪退。

// 获取当前app状态栏子视图
UIApplication *app = [UIApplication sharedApplication];
NSArray *children = [[[app valueForKeyPath:@"statusBar"] valueForKeyPath:@"foregroundView"] subviews];
int type = 0;
for (id child in children) {
    if ([child isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {
        type = [[child valueForKeyPath:@"dataNetworkType"] intValue];
    }
}

switch (type) {
    case 1:
        return @"2G";
    case 2:
        return @"3G";
    case 3:
        return @"4G";
    case 5:
        return @"WIFI";
    default:
        return @"Unknow";
        break;
}

不过你依然可以用AFNetworking类似的方式,获取网络状态:

SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

switch (self.networkReachabilityAssociation) {
    case AFNetworkReachabilityForName:
        break;
    case AFNetworkReachabilityForAddress:
    case AFNetworkReachabilityForAddressPair:
    default: {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
            SCNetworkReachabilityFlags flags;
            SCNetworkReachabilityGetFlags(self.networkReachability, &flags);
            AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
            dispatch_async(dispatch_get_main_queue(), ^{
                callback(status);

                NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
                [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:@{ AFNetworkingReachabilityNotificationStatusItem: @(status) }];


            });
        });
    }
        break;
}

static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
    BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
    BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
    BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
    BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
    BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));

    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
    if (isNetworkReachable == NO) {
        status = AFNetworkReachabilityStatusNotReachable;
    }
#if TARGET_OS_IPHONE
    else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
        status = AFNetworkReachabilityStatusReachableViaWWAN;
    }
#endif
    else {
        status = AFNetworkReachabilityStatusReachableViaWiFi;
    }

    return status;
}

2.状态栏

  1. 状态栏高度由原来的20,变为44了,以前使用常量的就被坑了。
  2. 在修改我们的宏过程中遇到了另外一个坑,特此提醒:
// 原有
#define MPStatusBarHeight (20)
// 新的
#define MPStatusBarHeight (UIApplication.sharedApplication.statusBarFrame.size.height)

/* 坑在此处 */
// application 的 statusBarFrame 在隐藏状态栏的时候是 CGRectZero
@property(nonatomic,readonly) CGRect statusBarFrame __TVOS_PROHIBITED; // returns CGRectZero if the status bar is hidden

3.友盟分享

友盟分享 SDK 6.4.6 版本提供的界面没有适配 iPhone X 需要更新 SDK 到最新的 6.8.0 版本,很遗憾的是,目前最新的友盟所有产品都没有提供 cocoapods 版本,需要手动集成。
在集成过程中遇到一个坑:
由于我们工程同时使用 pod 引用了友盟分享和友盟统计,本来只想升级友盟分享,于是直接跳到集成友盟分享的文档部分,执行了下面的操作:

1、移除pod对 UMShare的引用,执行 pod install
2、添加UMShareSDK到工程
3、修改文件引用错误
4、清除DerivedData
5、在模拟器build

报错:

image.png

询问了友盟客服(马上就回复了,很给力),是由于没有集成 common 基础库,回过头来看文档,发现集成友盟任意库都需要加入 common 基础库。
但是一旦加入 common 基础库,又会和使用 pod 引用的友盟统计冲突,无奈,只能统计库和分享库都换成手动引用。


2018.01.10更新:

友盟6.8.1版本已提供 cocoapods 集成方式,直接更新即可。

四、UIToolBar

UIToolBar 中两侧添加了 UIBarButtonItem,在 iOS 11
之前会布局在边,在 iOS 11 中两个item是紧挨着的,需要在中间添加 UIBarButtonSystemItemFlexibleSpace 类型的 item 才可以。

五、ALAssetsLibrary 保存图片闪退

在 iOS 11 之前,你可以直接使用下面代码来保存图片到相册:

#import <AssetsLibrary/AssetsLibrary.h>
// ALAssetsLibrary
[[[ALAssetsLibrary alloc] init] writeImageDataToSavedPhotosAlbum:imageData metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) {
    [self saveResultWithResult:(error == nil)];
}];

在 iOS 11中则会直接崩溃,保存图片的新姿势:

  • 解决方案1:
    在 infoPlist 文件中追加相册写入权限的提示
参数 key Xcode name 版本 说明
NSPhotoLibraryAddUsageDescription "Privacy - Photo Library Additions Usage Description" Specifies the reason for your app to get write-only access to the user’s photo library. See NSPhotoLibraryAddUsageDescription for details. iOS 11 and later 本参数iOS 11必须追加
NSPhotoLibraryUsageDescription “Privacy - Photo Library Usage Description” Specifies the reason for your app to access the user’s photo library. See NSPhotoLibraryUsageDescription for details. iOS 6.0 and later 本参数iOS 10以后必须追加

在infoPlist中直接添加(xcode 中 open as Source Code):

<key>NSPhotoLibraryUsageDescription</key>
<string>此 App 需要您的同意才能读取媒体资料库</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>此 App 需要您的同意才能写入媒体资料库</string>
  • 解决方案2:
    如果没有按方案1添加写入权限提示,ALAssetsLibrary 写入直接崩溃,而 PHPhotoLibrary 保存图片会直接触发向用户请求相册写入权限的 Alert 提示。
#import <Photos/Photos.h>
// PHPhotoLibrary
- (void)saveImageByPHPhotoLibrary:(UIImage *)image {
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        [PHAssetCreationRequest creationRequestForAssetFromImage:image];
    } completionHandler:^(BOOL success, NSError * _Nullable error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self saveResultWithResult:(success && error == nil)];
        });
    }];
}

更新

由于在应用中使用了 WKWebView , WKWebView 在没有处理的情况下,长按图片会弹出保存图片的选项,选择保存图片同样会崩溃。所以该方案不能处理好所有入口,推荐使用方案1。

image.png

六、键盘钥匙串 (password auto fill)

1、问题

在iOS 11系统中,你会发现你原本的一些输入框唤起系统键盘后出现如下的状况:


image.png

键盘右上角的🔑,是iOS 11的新特性 Password Auto Fill,假如你在代码中设置 UITextField 的 contentType 为 usernamepassword类型,就会出现上面的图标。

usernameTextField.textContentType = UITextContentType.username
passwordTextField.textContentType = UITextContentType.password

而有些情况,在 XIB 文件中,如果你没有指定特定类型,键盘也会出现🔑:

image.png

对应情况你只需要将 contentType 设置为其它类型即可:

image.png

2、关于 Password Auto Fill

Password Auto Fill 的适配可以参考iOS 11 ---- Password Auto Fill
(PS: 感觉机制有点类似UniversalLink)

七、react-native

可以参考下面代码,来适配状态栏和底部安全域:

import { Dimensions, Platform } from 'react-native'

const { width, height } = Dimensions.get('window')

export const isIphoneX = () => (
    Platform.OS === 'ios' &&
    !Platform.isPad &&
    !Platform.isTVOS &&
    (height === 812 || width === 812)
)

const getStatusBarHeight = () => {
  if (Platform.OS === 'android') {
    return 0
  }

  if (isIphoneX()) {
    return 44
  }

  return 20
}

// const
export const IPHONEX_BOTTOM_HEIGHT = 34
export const STATUSBAR_HEIGHT = getStatusBarHeight()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349