【iOS10】适配问题集合

由于Xcode8签名问题使原先的三方插件都不能用,尝试去除签名,但一些插件会导致Xcode8闪退,所以一直在Xcode7与Xocde8之间切换使用. 顺便整理一下旧项目中的各种iOS10与Xcode8的适配问题.


ATS问题

从iOS9开始, apple就强制使用https. iOS9中可以NSAllowsArbitraryLoads设置为YES禁用ATS。但是iOS 10从2017年1月1日起苹果不允许我们通过这个方法跳过ATS,也就是说强制我们用HTTPS,如果不这样的话提交App可能会被拒绝。
解决办法:
方法1: 后台接口全部采用https. (但总有例外的)
方法2: 通过NSExceptionDomains来针对特定的域名开放HTTP访问.
(每个域下面需要设置3个属性:NSIncludesSubdomains、NSExceptionRequiresForwardSecrecy、NSExceptionAllowsInsecureHTTPLoads。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>thatotherdomain.com</key>
<dict>
<!--适用于这个特定域名下的所有子域-->
<key>NSIncludesSubdomains</key>
<true/>
<!--扩展可接受的密码列表:这个域名可以使用不支持 forward secrecy 协议的密码-->
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<!--允许App进行不安全的HTTP请求-->
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<!--在这里声明所支持的 TLS 最低版本-->
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>

注意: 安全传输不再支持SSLv3, 建议尽快停用SHA1和3DES算法。
在iOS 10 中info.plist文件新加入了NSAllowsArbitraryLoadsInWebContent键,允许任意web页面加载,同时苹果会用 ATS 来保护你的app。

新的推送

iOS10引入了从UIKit独立出来的UserNotifications.framework, 用来替代原先的本地与远程推送. 同时也引入了UserNotificationsUI.framework允许你定制本地或远程通知出现在你的设备上时的外观。(旧的接口目前也仍能iOS10中正常使用)
典型变化: UserNotifications.framework文档

  1. 在iOS10中,即使app在前台也可以显示通知,播放声音、增加角标了.(这个省事了)
  2. 支持3DTouch替代手势滑动了。
    由于新的UserNotifications.framework出现, iOS10以前被废弃的类有:

    UILocalNotification
    UIMutableUserNotificationAction
    UIMutableUserNotificationCategory
    UIUserNotificationAction
    UIUserNotificationCategory
    UIUserNotificationSettings

使用UserNotifications.framework的例子:
1.引入 UserNotifications

1
@import UserNotifications;

2.为本地通知请求授权

1
2
3
4
5
6
7
8
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert)
completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!error) {
NSLog(@"request authorization succeeded!");
[self showAlert];
}
}];

3.更新应用红点示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = [NSString localizedUserNotificationStringForKey:@"Elon said:" arguments:nil];
content.body = [NSString localizedUserNotificationStringForKey:@"Hello Lilei!"
arguments:nil];
content.sound = [UNNotificationSound defaultSound];
/// 4. update application icon badge number
content.badge = @([[UIApplication sharedApplication] applicationIconBadgeNumber] + 1);
// Deliver the notification in five seconds.
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger
triggerWithTimeInterval:5.f repeats:NO];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"FiveSecond"
content:content trigger:trigger];
/// 3. schedule localNotification
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(@"add NotificationRequest succeeded!");
}
}];

移除本地通知也变得简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
// remove all local notification:
// [center removeAllPendingNotificationRequests];
// or you can remove specifical local notification:
[center removePendingNotificationRequestsWithIdentifiers:@[ CYLInviteCategoryIdentifier ]];
} else {
// remove all local notification:
// [[UIApplication sharedApplication] cancelAllLocalNotifications];
// or you can remove specifical local notification:
NSString *specificalIDToCancel = CYLInviteCategoryIdentifier;
UILocalNotification *notificationToCancel = nil;
for(UILocalNotification *aNotif in [[UIApplication sharedApplication] scheduledLocalNotifications]) {
if([[aNotif.userInfo objectForKey:@"CategoryIdentifier"] isEqualToString:specificalIDToCancel]) {
notificationToCancel = aNotif;
break;
}
}
if(notificationToCancel) {
[[UIApplication sharedApplication] cancelLocalNotification:notificationToCancel];
}
}

info.plist权限问题

当请求照相机等权限时会崩溃. 需要添加额外的用途说明.
错误描述: This app has crashed because it attempted to access privacy-sensitive data without a usage description.

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<!-- 🖼 Photo Library -->
<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME) photo use</string>
<!-- 📷 Camera -->
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) camera use</string>
<!-- 🎤 Microphone -->
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) microphone use</string>
<!-- 📍 Location -->
<key>NSLocationUsageDescription</key>
<string>$(PRODUCT_NAME) location use</string>
<!-- 📍 Location When In Use -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>$(PRODUCT_NAME) location use</string>
<!-- 📍 Location Always -->
<key>NSLocationAlwaysUsageDescription</key>
<string>$(PRODUCT_NAME) always uses location </string>
<!-- 📆 Calendars -->
<key>NSCalendarsUsageDescription</key>
<string>$(PRODUCT_NAME) calendar events</string>
<!-- ⏰ Reminders -->
<key>NSRemindersUsageDescription</key>
<string>$(PRODUCT_NAME) reminder use</string>
<!-- 📒 Contacts -->
<key>NSContactsUsageDescription</key>
<string>$(PRODUCT_NAME) contact use</string>
<!-- 🏊 Motion -->
<key>NSMotionUsageDescription</key>
<string>$(PRODUCT_NAME) motion use</string>
<!-- 💊 Health Update -->
<key>NSHealthUpdateUsageDescription</key>
<string>$(PRODUCT_NAME) heath update use</string>
<!-- 💊 Health Share -->
<key>NSHealthShareUsageDescription</key>
<string>$(PRODUCT_NAME) heath share use</string>
<!-- ᛒ🔵 Bluetooth Peripheral -->
<key>NSBluetoothPeripheralUsageDescription</key>
<string>$(PRODUCT_NAME) Bluetooth Peripheral use</string>
<!-- 🎵 Media Library -->
<key>NSAppleMusicUsageDescription</key>
<string>$(PRODUCT_NAME) media library use</string>
<!-- 📱 Siri -->
<key>NSSiriUsageDescription</key>
<string>$(PRODUCT_NAME) siri use</string>
<!-- 🏡 HomeKit -->
<key>NSHomeKitUsageDescription</key>
<string>$(PRODUCT_NAME) home kit use</string>
<!-- 📻 SpeechRecognition -->
<key>NSSpeechRecognitionUsageDescription</key>
<string>$(PRODUCT_NAME) speech use</string>
<!-- 📺 VideoSubscriber -->
<key>NSVideoSubscriberAccountUsageDescription</key>
<string>$(PRODUCT_NAME) tvProvider use</string>

有的应用可能还是有问题, 可以在info.plist中开启后台权限.

1
2
3
4
5
<key>UIBackgroundModes</key>
<array>
<!-- something you should use in background -->
<string>location</string>
</array>

或者去 target -> Capabilities -> Background Modes -> open the background Modes中开启.

iOS版本检查一点变化

以下这种版本检测方法失效. (substringToIndex:1在SDK‘iOS 10.0’(Xcode)中等于SDK‘iOS 1.0’)

1
#define IsIOS7 ([[[[UIDevice currentDevice] systemVersion] substringToIndex:1] intValue]>=7)

可以使用如下的替换:

1
2
3
4
5
#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

Swift中iOS版本检测:

1
2
3
4
5
if #available(iOS 10.0, *) {
// modern code
} else {
// Fallback on earlier versions
}

xib问题

新版的xib不能在旧版Xcode中打开了. 当在Xocde8中打开旧版的xib或storyboard时, 会提示:
This version does not support documents saved in the Xcode 8 format. Open this document with Xcode 8.0 or later

解决办法:
去掉如下代码:

1
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>

查找方法: 自己用Xcdoe7新建个项目, 再用Xcode8打开. 比一比xib源码的不同即可.

UIStatusBar方法过期

编译器会提示旧的方法不能用.
即使用preferredStatusBarStyle替代原先的setStatusBarStyle.

1
2
3
4
5
6
@property(nonatomic, readonly) UIStatusBarStyle preferredStatusBarStyle NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarStyleDefault
@property(nonatomic, readonly) BOOL prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO
- (UIStatusBarStyle)preferredStatusBarStyle NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarStyleDefault
- (BOOL)prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO
// Override to return the type of animation that should be used for status bar changes for this view controller. This currently only affects changes to prefersStatusBarHidden.
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarAnimationFade

其他适配问题或更新

1.Xcode8开始默认最低支持iOS8. 所以建议还是保留一份Xcode7的版本.
2.Xcode8第3方插件取消
3.UIRefreshControl可以直接在UICollectionView和UITableView中使用,并且脱离了UITableViewController.现在RefreshControl是UIScrollView的一个属性.
4.UICollectionViewCell性能优化.

总结

适配问题每次iOS大版本升级总是一堆堆的. 暂且整理这些.

转载请注明出处,有疑问欢迎留言!