iOS13系统上线后,我遇到了哪些问题?
新系统的发布,xcode的第一时间更新带来的问题
11月6日,除iOS13.3与iPadOS13.3开发者预览版Beta1外,苹果面向开发人员发布了Xcode11.2.1紧急更新。Xcode是用于开发iOS、macOS、watchOS和tvOS应用程序的工具。本次更新距Xcode 11.2发布仅一周时间。
苹果表示,在早期版本的iOS、iPadOS或tvOS运行时,使用UITextView的应用程序可能会崩溃。最新的Xcode 11.2.1解决了这一问题。
在我用商店下载的xcode11.2打包上架时候,苹果会拒绝并要求升级到11.2.1以上版本,并提示遭遇这一问题的开发人员应尽快更新Xcode。新版本目前在Apple Developer网站上可用,并很快将在Mac App Store上提供。(这句话扯到什么地步呢,也就是在我从开发者网站下载bete版本以后的几天,我都没发现Mac App store有更新 11.2.1)。
谈谈适配问题
- 支持的机型
iPhone X、iPhone XR、iPhone XS、iPhone XS Max
iPhone 8、iPhone 8 Plus
iPhone 7、iPhone 7 Plus
iPhone 6s、iPhone 6s Plus
iPhone SE
iPod touch (第七代)
UI层面
2. Dark(黑夜模式)
iOS 13 推出暗黑模式,UIKit 提供新的系统颜色和 api 来适配不同颜色模式,xcassets 对素材适配也做了调整,官方具体适配可见: Implementing Dark Mode on iOS。
iOS13-适配夜间模式/深色外观(Dark Mode)怎么动态改变颜色.
3. Sign In with Apple
- 苹果官方文档 【Sign In with Apple will be available for beta testing this summer. It will be required as an option for users in apps that support third-party sign-in when it is commercially available later this year.】
如果你的应用支持使用第三方登录,那么就必须加上苹果新推出的登录方式:Introducing Sign In with Apple。目前苹果只在 News and Updates 上提到正式发布时要求加上,具体发布时间还没确定。但苹果同样举出了几种特例的情况:
- 仅使用公司内部账号登陆。
- 教育或者企业应用,需要使用现有的教育和企业帐号登录。
- 应用需要使用政府或者行业背景的共名身份系统或者 电子ID 进行登录。
应用是特定的第三方客户服务客户端,需要使用邮箱,社交帐号,或者其他的三方服务来获取他们的内容。
4. 模态弹出默认交互改变(必须适配,强制要求)
- 在 iOS 13 中此枚举值直接成为了模态弹出的默认值,因此 presentViewController 方式打开视图是如下的视差效果,默认是下滑返回。
iOS13下仍然可以做到全屏弹出,这里需要UI决定采用哪种样式:
1 | //和以前一样全屏幕铺满状态,ios13里面默认会是-2,以前是0 |
有一点注意的是,ctr的生命周期方法调用情况会改变,假设有a,b两个ctr,在a中present出b:
全屏present时(UIModalPresentationFullScreen)的方法调用顺序:1
2
3
4a---viewWillDisappear:
b---viewWillAppear:
b---viewDidAppear:
a---viewDidDisappear:- dissmiss时的方法调用顺序:非全屏presnet时(UIModalPresentationPageSheet)的方法调用顺序:
1
2
3
4b---viewWillDisappear:
a---viewWillAppear:
a---viewDidAppear:
b---viewDidDisappear:
1
2b---viewWillAppear:
b---viewDidAppear:dissmiss时的方法调用顺序:
1
2b---viewWillDisappear:
b---viewDidDisappear:*可以看出,以UIModalPresentationPageSheet的方式来present/dismiss时,分别少调用了a的两个方法,如果之前在这个位置有相关的逻辑代码,比如网络请求,UI刷新,要注意
- dissmiss时的方法调用顺序:
5. UISegmentedControl 默认样式改变(必须适配,强制要求)
- 默认样式变为白底黑字,如果设置修改过颜色的话,页面需要修改。
原本设置选中颜色的 tintColor 已经失效,新增了 selectedSegmentTintColor 属性用以修改选中的颜色。
6. Web的适配,参考链接:
代码层面
1. 私有方法 KVC 不允许使用(必须适配,强制要求)
- 在 iOS 13 中不再允许使用 valueForKey、setValue:forKey: 等方法获取或设置私有属性,虽然编译可以通过,但是在运行时会直接崩溃,并提示一下崩溃信息:
1 | // 使用的私有方法 |
1 | // 崩溃提示信息 |
解决方案:
1 | // 替换的方案 |
2. 推送的 deviceToken 获取到的格式发生变化(必须适配,强制要求)
- 原本可以直接将 NSData 类型的 deviceToken 转换成 NSString 字符串,然后替换掉多余的符号即可:
1 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { |
在 iOS 13 中,这种方法已经失效,NSData类型的 deviceToken 转换成的字符串变成了:
1
{length = 32, bytes = 0xd7f9fe34 69be14d1 fa51be22 329ac80d ... 5ad13017 b8ad0736 }
需要进行一次数据格式处理,参考友盟的做法,可以适配新旧系统,获取方式如下:
1
2
3
4
5
6
7
8
9
10#include <arpa/inet.h>
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
if (![deviceToken isKindOfClass:[NSData class]]) return;
const unsigned *tokenBytes = [deviceToken bytes];
NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
NSLog(@"deviceToken:%@", hexToken);
}3. UISearchBar 黑线处理导致崩溃
之前为了处理搜索框的黑线问题,通常会遍历 searchBar 的 subViews,找到并删除 UISearchBarBackground,在 iOS13 中这么做会导致 UI 渲染失败,然后直接崩溃,崩溃信息如下:
1
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Missing or detached view for search bar layout'
- 解决办法是设置 UISearchBarBackground 的 layer.contents 为 nil:
1
2
3
4
5
6
7for (UIView *view in _searchBar.subviews.lastObject.subviews) {
if ([view isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) {
// [view removeFromSuperview];
view.layer.contents = nil;
break;
}
}4. 使用 UISearchDisplayController 导致崩溃
- 在 iOS 8 之前,我们在 UITableView 上添加搜索框需要使用 UISearchBar + UISearchDisplayController 的组合方式,而在 iOS 8 之后,苹果就已经推出了 UISearchController 来代替这个组合方式。在 iOS 13 中,如果还继续使用 UISearchDisplayController 会直接导致崩溃,崩溃信息如下:
- 解决办法是设置 UISearchBarBackground 的 layer.contents 为 nil:
1 | *** Terminating app due to uncaught exception 'NSGenericException', reason: 'UISearchDisplayController is no longer supported when linking against this version of iOS. Please migrate your application to UISearchController.' |
- 另外说一下,在 iOS 13 中终于可以获取直接获取搜索的文本框:
1
_searchBar.searchTextField.text = @“search";
5. MPMoviePlayerController 被弃用
- 在 iOS 9 之前播放视频可以使用 MediaPlayer.framework 中的MPMoviePlayerController类来完成,它支持本地视频和网络视频播放。但是在 iOS 9 开始被弃用,如果在 iOS 13 中继续使用的话会直接抛出异常:
1
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
6. LaunchImage 被弃用(必须)
- iOS 8 之前我们是在LaunchImage 来设置启动图,但是随着苹果设备尺寸越来越多,我们需要在对应的 aseets 里面放入所有尺寸的启动图,这是非常繁琐的一个步骤。因此在 iOS 8 苹果引入了 LaunchScreen.storyboard,支持界面布局用的 AutoLayout + SizeClass ,可以很方便适配各种屏幕。
需要注意的是,苹果在 Modernizing Your UI for iOS 13 section 中提到,从2020年4月开始,所有支持 iOS 13 的 App 必须提供 LaunchScreen.storyboard,否则将无法提交到 App Store 进行审批。
7. Xcode 11 创建的工程在低版本设备上运行黑屏
- 使用 Xcode 11 创建的工程,运行设备选择 iOS 13.0 以下的设备,运行应用时会出现黑屏。这是因为 Xcode 11 默认是会创建通过 UIScene 管理多个 UIWindow 的应用,工程中除了 AppDelegate 外会多一个 SceneDelegate.
这是为了 iPadOS 的多进程准备的,也就是说 UIWindow 不再是 UIApplication 中管理。但是旧版本根本没有 UIScene,因此解决方案就是在 AppDelegate 的头文件加上:
1 | @property (strong, nonatomic) UIWindow *window; |
8. 使用 @available 导致旧版本 Xcode 编译出错。(必须)
在 Xcode 11 的 SDK 工程的代码里面使用了 @available 判断当前系统版本,打出来的包放在 Xcode 10 中编译,会出现一下错误:
1 | Undefine symbols for architecture i386: |
从错误信息来看,是 __isPlatformVersionAtLeast 方法没有具体的实现,但是工程里根本没有这个方法。实际测试无论在哪里使用@available ,并使用 Xcode 11 打包成动态库或静态库,把打包的库添加到 Xcode 10 中编译都会出现这个错误,因此可以判断是 iOS 13 的 @available 的实现中使用了新的 api。如果你的 SDK 需要适配旧版本的 Xcode,那么需要避开此方法,通过获取系统版本来进行判断:
1 | if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) { |
另外,在 Xcode 10 上打开 SDK 工程也应该可以正常编译,这就需要加上编译宏进行处理:
1 | #ifndef __IPHONE_13_0 |
9. NSAttributedString优化
- 对于UILabel、UITextField、UITextView,在设置NSAttributedString时也要考虑适配Dark Mode,否则在切换模式时会与背景色融合,造成不好的体验/推荐的做法:
1
2
3// 添加一个NSForegroundColorAttributeName属性
NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:[UIColor labelColor]};
NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];10. 废弃UIWebView(必须)
- UIWebView在12.0就已经被废弃,部分APP使用webview时, 审核被拒
目前提交苹果应用市场(App Store)会反馈以下邮件提示:查找哪些SDK包含了 UIWebView1
2ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs .
See developer.apple.com/documentati… for more information.目前常用的SDK中,已知更新移除 UIWebView 的版本有:1
find . -type f | grep -e ".a" -e ".framework" | xargs grep -s UIWebView
- QQ登录 (v3.3.6)
- ShareSDK (v4.3.2)
- 极验证 (v0.12.5)
- 新浪微博(v3.2.5)
- 微信开放平台 (v1.8.6.1)
暂未更新移除 UIWebView 版本的 SDK :
- 谷歌广告
部分关于 UIWebView 审核问题的讨论:
dcloud Cocos2d-x ShareSDK
11. WKWebView 中测量页面内容高度的方式变更
- iOS 13以前 document.body.scrollHeight iOS 13中 document.documentElement.scrollHeight 两者相差55 应该是浏览器定义高度变了
12. 蓝牙权限需要申请
CBCentralManager,iOS13以前,使用蓝牙时可以直接用,不会出现权限提示,iOS13后,再使用就会提示了。 在info.plist里增加
1
2<key>NSBluetoothAlwaysUsageDescription</key>
<string>我们要一直使用您的蓝牙</string>在iOS13中,蓝牙变成了和位置,通知服务等同样的可以针对单个app授权的服务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37- (NSString*) getWifiSsid {
if (@available(iOS 13.0, *)) {
//用户明确拒绝,可以弹窗提示用户到设置中手动打开权限
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
NSLog(@"User has explicitly denied authorization for this application, or location services are disabled in Settings.");
//使用下面接口可以打开当前应用的设置页面
//[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
return nil;
}
CLLocationManager* cllocation = [[CLLocationManager alloc] init];
if(![CLLocationManager locationServicesEnabled] || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined){
//弹框提示用户是否开启位置权限
[cllocation requestWhenInUseAuthorization];
usleep(50);
//递归等待用户选选择
return [self getWifiSsidWithCallback:callback];
}
}
NSString *wifiName = nil;
CFArrayRef wifiInterfaces = CNCopySupportedInterfaces();
if (!wifiInterfaces) {
return nil;
}
NSArray *interfaces = (__bridge NSArray *)wifiInterfaces;
for (NSString *interfaceName in interfaces) {
CFDictionaryRef dictRef = CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interfaceName));
if (dictRef) {
NSDictionary *networkInfo = (__bridge NSDictionary *)dictRef;
NSLog(@"network info -> %@", networkInfo);
wifiName = [networkInfo objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID];
CFRelease(dictRef);
}
}
CFRelease(wifiInterfaces);
return wifiName;
}13. CNCopyCurrentNetworkInfo
iOS13 以后只有开启了 Access WiFi Information capability,才能获取到 SSID 和 BSSID wi-fi or wlan 相关使用变更
最近收到了苹果的邮件,说获取WiFi SSID的接口CNCopyCurrentNetworkInfo 不再返回SSID的值。不仔细看还真会被吓一跳,对物联网的相关APP简直是炸弹。仔细看邮件还好说明了可以先获取用户位置权限才能返回SSID。
注意:目本身已经打开位置权限,则可以直接获取
14. MJExtension 问题
- Stack overflow in +[NSObject(Property) mj_properties] 升级 MJExtension 至 3.1.0版本以上,弃用老方法,涉及到所有方法添加 mj_ 前缀。