# iOS 13 兼容性适配检查

### 可能出现问题的关键字&#x20;

复制下面的关键字在 Xcode 中正则搜索

```
(_UINavigationBarContentView|forKeyPath|_placeholderLabel|boolForKey|stringForKey|deviceToken|CNCopyCurrentNetworkInfo|presentViewController|UISearchDisplayController|UIWebView|MPMoviePlayerController|@available|_cancelButtonText|_searchField)
```

![](https://3933803984-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lw8fe5ohtBLKudCKXmM%2F-LxurEdLBcoWcT5FjIDY%2F-Lxus2asvloJEWq_APgp%2Fimage.png?alt=media\&token=7a1234b5-3ac2-49c6-a48b-3a02ecb297f3)

#### **\_cancelButtonText**:

crash: \[searchBar setValue:@”取消” forKey:@”\_cancelButtonText”];

#### **\_UINavigationBarContentView**

```
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@”_UINavigationBarContentView”]) {
subview.layoutMargins = UIEdgeInsetsZero;
break;
}
}
```

这种做法在 iOS 13 中会导致崩溃，崩溃信息如下\
\*\*\* Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Client error attempting to change layout margins of a private view’

**解决方案**\
使用设置 frame 的方式，让 \_UINavigationBarContentView 向两边伸展，从而抵消两边的边距。

#### **\_placeholderLabel**

\[\_textField setValue:\[UIColor redColor] forKeyPath:@”\_placeholderLabel.textColor”];///崩溃\
\[\_textField setValue:\[UIFont systemFontOfSize:14] forKeyPath:@”\_placeholderLabel.font”];///崩溃**解决方案**\
\_textField.attributedPlaceholder = \[\[NSAttributedString alloc] initWithString:@”姓名” attributes:@{NSFontAttributeName:\[UIFont systemFontOfSize:14],NSForegroundColorAttributeName:\[UIColor redColor]}];在Xcode10上编译不会有问题，但在Xcode11上编译的会崩溃。并且- (void)setValue:(nullable id)value forKey:(NSString \*)key方法没问题，- (void)setValue:(nullable id)value forKeyPath:(NSString \*)keyPath会崩溃

#### **boolForKey**

第三方 NSDictionary 扩展重名\
打印日志：objc\[5337]: REPLACED: -\[NSDictionary boolForKey:]  by category HMFoundation  (IMP was 0x104ae8280 (/var/containers/Bundle/Application/B1486820-C2CC-4339-9D41-DBF1AC77EBF2/iHome4iPhone.app/iHome4iPhone), now 0x186d95a8c (/System/Library/PrivateFrameworks/HMFoundation.framework/HMFoundation))**解决方案**\
Category 换名字

#### **stringForKey**

stringForKey 返回的是默认值是@“”并非nil.\
if(\[dict stringForKey:@”key”]){} 会发生逻辑错误**解决方案**\
\[dict stringForKey:@”key”].length > 0

#### **Category**

见 \<Category 私有函数>

#### **deviceToken**

```
– (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *token = [deviceToken description];
for (NSString *symbol in @[@” “, @”<“, @”>”, @”-“]) {
token = [token stringByReplacingOccurrencesOfString:symbol withString:@””];
}
}
```

在 iOS 13 中，这种方法已经失效，NSData类型的 deviceToken 转换成的字符串变成了：\
{length = 32, bytes = 0xd7f9fe34 69be14d1 fa51be22 329ac80d … 5ad13017 b8ad0736 }

**解决方案**

`#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);`\
`}`<br>

#### **presentViewController**

苹果将 UIViewController 的 modalPresentationStyle 属性的默认值改成了新加的一个枚举值  UIModalPresentationAutomatic，对于多数 UIViewController，此值会映射成 UIModalPresentationPageSheet。\
这种效果弹出来的页面导航栏部分是会被砍掉的，在 storyboard 中也可以看到，页面布局时需要注意导航栏的内容不要被遮挡。注意，我们原来以全屏的样式弹出一个页面，那么将这个页面弹出的那个 ViewController 会依次调用 viewWillDisappear 和 viewDidDisappear。然后在这个页面被 dismiss 的时候，将他弹出的那个 ViewController 的 viewWillAppear 和 viewDidAppear 会被依次调用。然而使用默认的视差效果弹出页面，将他弹出的那个 ViewController 并不会调用这些方法，原先写在这四个函数中的代码以后都有可能会存在问题。**解决方案：**&#x5982;果视差效果的样式可以接受的话，就不需要修改；如果需要改回全屏显示的界面，需要手动设置弹出样式：

```
– (UIModalPresentationStyle)modalPresentationStyle {
return UIModalPresentationFullScreen;
}
```

或 `self.modalPresentationStyle = .fullScreen`

#### **CNCopyCurrentNetworkInfo**

CNCopyCurrentNetworkInfo 文档说明，应用还需要符合下列三项条件中的至少一项才能得到正确的值：使用 Core Location 的应用， 并获得定位服务权限。\
使用 NEHotspotConfiguration 来配置 WiFi 网络的应用。\
目前正处于启用状态的 VPN 应用。解决方案:\
获取 wift信息还要先获得位置授权

#### **UISearchDisplayController**

在 iOS 8 之前，我们在 UITableView 上添加搜索框需要使用 UISearchBar + UISearchDisplayController 的组合方式，而在 iOS 8 之后，苹果就已经推出了 UISearchController 来代替这个组合方式。在 iOS 13 中，如果还继续使用 UISearchDisplayController 会直接导致崩溃，崩溃信息如下：\
\*\*\* 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.’

**解决方案:**\
使用 UISearchController 替换 UISearchBar + UISearchDisplayController 的组合方案。

#### **UIWebView**

UIWebView 将被禁止提交审核\
如果开发者将包含 UIWebView api 的应用更新上传到 App Store 审核后，其将会收到包含 ITMS-90809 信息的回复邮件**解决方案**\
用 WKWebView 替代 UIWebView，确保所有 UIWebView 的 api 都要移除，如果需要适配 iOS 7 的可以通过 openURL 的方式在 Safari 打开。

#### **MPMoviePlayerController**

在 iOS 9 之前播放视频可以使用 MediaPlayer.framework 中的MPMoviePlayerController类来完成，它支持本地视频和网络视频播放。但是在 iOS 9 开始被弃用，如果在 iOS 13 中继续使用的话会直接抛出异常：\*\*\* Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.’

**解决方案**

使用 AVFoundation 里的 AVPlayer 作为视频播放控件。

#### **@available**

使用 @available 导致旧版本 Xcode 编译出错\
在 Xcode 11 的 SDK 工程的代码里面使用了 @available 判断当前系统版本，打出来的包放在 Xcode 10 中编译，会出现一下错误：Undefine symbols for architecture i386:\
“\_\_isPlatformVersionAtLeast”, referenced from:\
…\
ld: symbol(s) not found for architecture i386复制代码从错误信息来看，是 \_\_isPlatformVersionAtLeast 方法没有具体的实现，但是工程里根本没有这个方法。实际测试无论在哪里使用@available ，并使用 Xcode 11 打包成动态库或静态库，把打包的库添加到 Xcode 10 中编译都会出现这个错误，因此可以判断是 iOS 13 的 @available 的实现中使用了新的 api。\
\
**解决方案**\
如果你的 SDK 需要适配旧版本的 Xcode，那么需要避开此方法，通过获取系统版本来进行判断：\
if (\[UIDevice currentDevice].systemVersion.floatValue >= 13.0) {\
…\
}另外，在 Xcode 10 上打开 SDK 工程也应该可以正常编译，这就需要加上编译宏进行处理：

\#ifndef \_\_IPHONE\_13\_0\
\#define \_\_IPHONE\_13\_0 130000\
\#endif

\#if \_\_IPHONE\_OS\_VERSION\_MAX\_ALLOWED >= \_\_IPHONE\_13\_0\
…\
\#endif

#### **\_searchField**

UITextField \*textField = \[searchBar valueForKey:@”\_searchField”]; // Crash

**解决方案**

// 替代方案 1，使用 iOS 13 的新属性 searchTextField\
searchBar.searchTextField.placeholder = @”search”;

#### **\_cancelButtonText**

\[searchBar setValue:@”取消” forKey:@”\_cancelButtonText”]; // Crash

**解决方案**\
// 替代方案，用同上的方法找到子类中 UIButton 类型的属性，然后设置其标题\
`UIButton *cancelButton = [self findViewWithClassName:NSStringFromClass([UIButton class]) inView:searchBar];`\
`[cancelButton setTitle:@”取消” forState:UIControlStateNormal];`

#### Category 私有函数

| 类              | Category函数               | 系统Category仓库           |
| -------------- | ------------------------ | ---------------------- |
| NSObject       | safeValueForKey          | AccessibilityUtilities |
| NSDictionary   | boolForKey               | HMFoundation           |
| NSDictionary   | stringForKey             | HMFoundation           |
| NSDictionary   | arrayForKey              | HMFoundation           |
| NSDictionary   | dictionaryForKey         | HMFoundation           |
| NSDictionary   | dataForKey               | HMFoundation           |
| NSDictionary   | numberForKey             | HMFoundation           |
| NSDictionary   | mutableDictionaryForKey  | HMFoundation           |
| NSDictionary   | mutableArrayForKey       | HMFoundation           |
| NSString       | unsignedLongLongValue    | HearingUtilities       |
| NSString       | unsignedLongLongValue    | HearingUtilities       |
| NSString       | stdStringForString       | libwebrtc              |
| NSString       | stringForStdString       | libwebrtc              |
| NSString       | unsignedIntValue         | IMFoundation           |
| NSString       | stdString                | GeoServices            |
| NSString       | containsString           | Foundation             |
| NSString       | unsignedIntValue         | IMFoundation           |
| NSString       | hexValue                 | IMFoundation           |
| NSString       | hasPrefixCaseInsensitive | CalendarFoundation     |
| NSString       | urlEncodedString         | Social                 |
| NSMutableArray | nonRetainingArray        | IMFoundation           |

#### 关键字搜索

```
(safeValueForKey|boolForKey|stringForKey|arrayForKey|dictionaryForKey|dataForKey|numberForKey|mutableDictionaryForKey|mutableArrayForKey|unsignedLongLongValue|stdStringForString|stringForStdString|unsignedIntValue|stdString|hexValue|hasPrefixCaseInsensitive|urlEncodedString|nonRetainingArray)
```
