一、safeArea
-
automaticallyAdjustsScrollViewInsets
tocontentInsetAdjustmentBehavior
在iOS 11中,苹果废弃了UIViewController
的automaticallyAdjustsScrollViewInsets
属性,改用UIScrollView
的contentInsetAdjustmentBehavior
属性来替换它。
// 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
做屏幕适配,第一步先要添加启动图。
二、UITableView
自动计算高度
在iOS 11中,UITableView
的 estimatedRowHeight
、
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
列表是手动计算的行高,突然出现了以往只有开启自动计算行高才会出现的滚动条跳动的现象,给 TableView
的 contentSize
加了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
中,由于估算行高,互动到底部时,依然会触发 tableView
的 contentSize
和 contentOffset
变化,同时会触发 refreshingBlock
, 和 tableView
的 reloadData
,导致了最后一页数据陷入了循环请求。
引申问题
如果存在使用监听 tableView
的 contentSize
和 contentOffset
变化来触发的一些事件,最好把 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.状态栏
- 状态栏高度由原来的20,变为44了,以前使用常量的就被坑了。
- 在修改我们的宏过程中遇到了另外一个坑,特此提醒:
// 原有
#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
报错:
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
// 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):
NSPhotoLibraryUsageDescription
此 App 需要您的同意才能读取媒体资料库
NSPhotoLibraryAddUsageDescription
此 App 需要您的同意才能写入媒体资料库
-
解决方案2:
如果没有按方案1添加写入权限提示,ALAssetsLibrary
写入直接崩溃,而PHPhotoLibrary
保存图片会直接触发向用户请求相册写入权限的 Alert 提示。
#import
// 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。
- 解决方案3:
更为合理的做法是,在向相册写入图片之前,首先请求相册权限,参考iOS相册、相机、通讯录权限获取
六、键盘钥匙串 (password auto fill)
1、问题
在iOS 11系统中,你会发现你原本的一些输入框唤起系统键盘后出现如下的状况:
键盘右上角的,是iOS 11的新特性 Password Auto Fill
,假如你在代码中设置 UITextField
的 contentType 为 username
和 password
类型,就会出现上面的图标。
usernameTextField.textContentType = UITextContentType.username
passwordTextField.textContentType = UITextContentType.password
而有些情况,在 XIB
文件中,如果你没有指定特定类型,键盘也会出现:
对应情况你只需要将 contentType
设置为其它类型即可:
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()