iOS 13适配 适配指南
推送修改
原本可以直接将 NSData
类型的 deviceToken
转换成 NSString
字符串,然后替换掉多余的符号即可:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *token = [deviceToken description];
for (NSString *symbol in @[@" ", @"<", @">", @"-"]) {
token = [token stringByReplacingOccurrencesOfString:symbol withString:@""];
}
NSLog(@"deviceToken:%@", token);
}
复制代码
在 iOS 13 中,这种方法已经失效,NSData
类型的 deviceToken 转换成的字符串变成了:
{length = 32, bytes = 0xd7f9fe34 69be14d1 fa51be22 329ac80d ... 5ad13017 b8ad0736 }
复制代码
解决方案
需要进行一次数据格式处理,参考友盟的做法,可以适配新旧系统,获取方式如下:
#include
- (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);
}
复制代码
Xcode11 提交IAP包到开发者后台不展示构建版本
如果在升级Xcode11后,提交IAP包到开发者却长时间没有展示构建版本,去翻阅对应的邮箱邮件可能会有如下邮件:
**ITMS-90683: Missing Purpose String in Info.plist** - Your app's code references one or more APIs that access sensitive user data. The app's Info.plist file should contain a NSBluetoothAlwaysUsageDescription key with a user-facing purpose string explaining clearly and completely why your app needs the data. Starting Spring 2019, all apps submitted to the App Store that access user data are required to include a purpose string. If you're using external libraries or SDKs, they may reference APIs that require a purpose string. While your app might not use these APIs, a purpose string is still required. You can contact the developer of the library or SDK and request they release a version of their code that doesn't contain the APIs. Learn more ([https://developer.apple.com/documentation/uikit/core_app/protecting_the_user_s_privacy).](https://developer.apple.com/documentation/uikit/core_app/protecting_the_user_s_privacy). "https://developer.apple.com/documentation/uikit/core_app/protecting_the_user_s_privacy).")
After you’ve corrected the issues, you can use Xcode or Application Loader to upload a new binary to App Store Connect.
原因:
iOS13中新增蓝牙权限,需要添加对应权限申请说明。
解决方式:
target - info.plist下添加如下键值对:
Privacy - Bluetooth Always Usage Description
: 使用用途
止 KVC 对系统API私有属性的设置
下面方式对 UITextField 设置placeholder的颜色会产生崩溃
[_textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor”];
修改为:
[_textField setAttributedPlaceholder:<#(NSAttributedString * _Nullable)#>];
KVC获取状态栏(_statusBar)会导致崩溃
UIApplication *app = [UIApplication sharedApplication]; if ([[app valueForKeyPath:@"_statusBar"] isKindOfClass:NSClassFromString(@"UIStatusBar_Modern")]) {
NSArray *views = [[[[app valueForKeyPath:@"statusBar"] valueForKeyPath:@"statusBar"] valueForKeyPath:@"foregroundView"] subviews];
同理其他私有属性的读取也建议进行修改
UITextField leftView、rightView
问题:
- 在设置leftView 左按钮时,如果使用的是UIImageView即会出现图片无法按照意图显示的问题。
解决:
- UIImageVIew 包一层UIView再设置给leftView 、设置leftView或rightView不要使用约束布局
问题:
- iOS13 rightView纯文本按钮可能无法正常显示,按钮长度变短。
解决:
- 重新修改
- (CGRect)rightViewRectForBounds:(CGRect)bounds
大小
暗黑模式适配
具体适配的文章很多,这里就不做展开了。
着重说一点,如果没有需要支持的暗黑模式的需求,只需要在target 下的 info.plist中添加以下key & value
key: User Interface Style
value : Light
其他
- iOS 13 不再支持
UIApplicationExitsOnSuspend
。需要更新应用以处理现代多任务处理。
iOS13 UIKit & API修改 & 一些细节
1. 新增部分Color,如下示例
@property (class, nonatomic, readonly) UIColor *systemGray2Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray3Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray4Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray5Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray6Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
#pragma mark Foreground colors
/* Foreground colors for static text and related elements.
*/
@property (class, nonatomic, readonly) UIColor *labelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *secondaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *tertiaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *quaternaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
2. 根据系统设置 LightMode 和 DarkMode 状态取色
UIColor *backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * traitCollection) {
self->_userInterfaceStyle = traitCollection.userInterfaceStyle;
[weakSelf performSelector:@selector(traitCollectionChanged:) withObject:traitCollection];
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor blueColor];
}
return [UIColor yellowColor];
}];
3.UIActivityIndicatorView loading菊花修改
iOS13之前的三种样式:
-
UIActivityIndicatorViewStyleGray
灰色 -
UIActivityIndicatorViewStyleWhite
白色 -
UIActivityIndicatorViewStyleWhiteLarge
白色(大型)
iOS13废弃了以上三种样式,用以下两种样式代替:
-
UIActivityIndicatorViewStyleLarge
(大型) -
UIActivityIndicatorViewStyleMedium
(中型)
iOS13通过color属性设置颜色
4. statusBar修改
typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
UIStatusBarStyleDefault = 0, // Automatically chooses light or dark content based on the user interface style
UIStatusBarStyleLightContent API_AVAILABLE(ios(7.0)) = 1, // Light content, for use on dark backgrounds
UIStatusBarStyleDarkContent API_AVAILABLE(ios(13.0)) = 3, // Dark content, for use on light backgrounds
UIStatusBarStyleBlackTranslucent NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 1,
UIStatusBarStyleBlackOpaque NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 2,
} API_UNAVAILABLE(tvos);
在iOS13之前有两种状态,default
和lightContent
在iOS13 有三种状态,default,
darkContent
和 lightContent
darkContent 对应之前的 default
,
default 会根据情况自动选择 darkContent
和 lightContent
5. [UIApplication sharedApplication].keyWindow API将被弃用
@property(nullable, nonatomic,readonly) UIWindow *keyWindow API_DEPRECATED("Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes", ios(2.0, 13.0));
iOS13 UIApplication keyWindow被标记为API_DEPRECATED,
修改使用下方代码获取
[[[UIApplication sharedApplication] windows] objectAtIndex:0]
6. 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代替
7.MPMoviePlayerController 弃用
MPMoviePlayerController 在 iOS 9 开始被弃用,如果在 iOS 13 中继续使用的话会直接抛出异常:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
解决办法:使用 AVPlayer 作为视频播放控件
8. 模态弹出默认样式改变 UIModalPresentationStyle
在 iOS 13,使用 presentViewController 方式打开视图,会跟导航栏有部分视觉差,如下图
原因是:苹果将 UIViewController 的 modalPresentationStyle 属性的默认值改成了新加的一个枚举值 UIModalPresentationAutomatic,对于多数 UIViewController,此值会映射成 UIModalPresentationPageSheet。
解决办法: 可以在vcpresent之前设置modalPresentationStyle 为 UIModalPresentationFullScreen
另外,present的vc用户下拉可以dissmiss控制器,如果不想要这效果,可以这样设置
/*当该属性为 false 时,用户下拉可以 dismiss 控制器,为 true 时,下拉不可以 dismiss控制器*/
xxVC.isModalInPresentation = true
还有一点需要注意,原来以UIModalPresentationFullScreen
样式弹出页面,那么这个页面弹出 ViewController 会依次调viewWillDisappear
和 viewDidDisappear
。然后在这个页面被 dismiss 的时候,将他弹出的那个 ViewController 的 viewWillAppear
和 viewDidAppear
会被依次调用。然而使用默认的视差效果弹出页面,将他弹出的那个 ViewController 并不会调用这些方法,原先写在这四个函数中的代码以后都有可能会存在问题。
9.蓝牙权限相关修改
升级iOS13的用户很明显的体验就是,很多APP都会申请蓝牙权限,这与本次修改有关。
在 iOS 13 中,苹果将原来蓝牙申请权限用的 NSBluetoothPeripheralUsageDescription
字段,替换为 NSBluetoothAlwaysUsageDescription
字段。
For apps with a deployment target of iOS 13 and later, use NSBluetoothAlwaysUsageDescription instead.
10. iOS13彻底废弃UIWebview
iOS13 提交APP时,会提示UIWebview为过期API,会发送邮件提示你在下一次提交时将应用中UIWebView 的 api 移除。
Dear Developer,
We identified one or more issues with a recent delivery for your app, "xxx". Your delivery was successful, but you may wish to correct the following issues in your next delivery:
ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs . See [developer.apple.com/documentati…]([https://developer.apple.com/documentation/uikit/uiwebview](https://developer.apple.com/documentation/uikit/uiwebview)
) for more information.
After you’ve corrected the issues, you can use Xcode or Application Loader to upload a new binary to App Store Connect.
Best regards,
The App Store Team
暂时没有强制使用WKWebView,但是在iOS13开始UIWebView已是废弃的API,以后更高的版本中防止出现问题,应及时替换。
11. 第三方登录支持苹果登录 Sign In with Apple
在 iOS 13 中苹果推出一种在 App 和网站上便捷登录的方式: Sign In With Apple,这是 iOS 13 新增的功能,因此需要使用 Xcode 11 进行开发。
2020 年 4 月之前,Apple 平台 App 需接入 Sign in with Apple
苹果在上周更新了审核指南,加入 4.8 Sign in with Apple 一条,要求所有专门使用第三方登录的 App,都必须接入 Sign in with Apple。符合以下条件的App,可以不接入:
- 使用自建账户和登录系统;
- 要求用户使用现有的教育或企业账户登录的教育、企业或商业类应用;
- 使用政府或行业支持的公民身份识别系统或电子 ID 来验证用户;
- 应用特定于第三方服务,用户需要使用邮箱、社交媒体或其它第三方账户才能访问其内容的应用;
12. LaunchImage 修改(iOS 7.0–13.0)
UILaunchImages has been deprecated; use Xcode launch storyboards instead. For more information on how to construct and format your launch storyboard, seeLaunch Screen
从2020年4月开始,所有支持 iOS 13 的 App 必须提供 LaunchScreen.storyboard,否则将无法提交到 App Store 进行审批。
13. UISearchBar 黑线处理导致崩溃
iOS13之前为了处理搜索框的黑线问题,通常会遍历 searchBar 的 subViews,找到并删除 UISearchBarBackground。
修改为:
for (UIView *view in _searchBar.subviews.lastObject.subviews) {
if ([view isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) {
view.layer.contents = nil;
break;
}
}
设置 UISearchBarBackground 的 layer.contents 为 nil。
14.状态栏获取方法
UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
15. TabBar红点偏移处理
如果之前有通过TabBar上图片位置来设置红点位置,在iOS13上会发现显示位置都在最左边。
遍历UITabBarButton的subViews,只有在TabBar选中状态下才能取到UITabBarSwappableImageView。
解决办法:通过UITabBarButton的位置来设置红点的frame
16. UIScrollView 滚动条异常偏移
添加以下代码
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
self.automaticallyAdjustsScrollIndicatorInsets = NO;
}
#endif
17. UINavigationBar 设置按钮边距导致崩溃
从 iOS 11 开始,UINavigationBar
使用了自动布局,左右两边的按钮到屏幕之间会有 16 或 20 的边距。
为了避免点击到间距的空白处没有响应,通常做法是:定义一个 UINavigationBar
子类,重写 layoutSubviews
方法,在此方法里遍历 subviews 获取 _UINavigationBarContentView
,并将其 layoutMargins
设置为 UIEdgeInsetsZero
。
- (void)layoutSubviews {
[super layoutSubviews];
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
向两边伸展,从而抵消两边的边距。
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@"_UINavigationBarContentView"]) {
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
UIEdgeInsets margins = subview.layoutMargins;
subview.frame = CGRectMake(-margins.left, -margins.top, margins.left + margins.right + subview.frame.size.width, margins.top + margins.bottom + subview.frame.size.height);
} else {
subview.layoutMargins = UIEdgeInsetsZero;
}
break;
}
}
}
18.UISegmentedControl 默认样式改变
默认样式变为白底黑字,如果设置修改过颜色的话,页面需要修改。
原本设置选中颜色的 tintColor
已经失效,新增了 selectedSegmentTintColor
属性用以修改选中的颜色。
转载部分 (以下转载自知识小集)
UIKit
当单元格突出显示或选中时,
UITableViewCell
类不再更改contentView
及其任何子视图的backgroundColor
或opaque
属性。如果要在contentView
内部(包括)内容的任何子视图上设置不透明的backgroundColor
,则单元格突出显示或选中时的外观可能会受到影响。解决子视图任何问题的最简单方法是确保将backgroundColor
设置为 nil 或 clearColor,并且设置它们的 opaque 属性为 false。但是,如果需要,您可以重写setHighlighted:animated:
和setSelected:animated:
方法,以便在移动到突出显示的状态和选定状态时手动更改子视图上的这些属性。从iOS 8开始,将
UISearchController
与 UINavigationController 一起使用需要将顶视图控制器的definesPresentationContext
属性设置为 true。如果不这样做会导致难以检测和调试的细微错误。从 iOS & iPadOS 13 beta 开始,如果视图控制器的 navigationItem 具有 non-nil 搜索控件,当视图控制器显示在导航控制器中时,UINavigationController
会自动将该视图控制器的definesPresentationContext
属性设置为 true。如果您要定位早期版本的 iOS,请在搜索控制器变为活动状态之前设置此属性。UIRefreshControl
类不再直接修改其滚动视图的contentInset
。 相反,它对内容插入的调整将合并到滚动视图的adjustContentInset
中。 唯一的例外是当滚动视图的contentInsetAdjustmentBehavior
设置为UIScrollViewContentInsetAdjustmentNever
时,在这种情况下,UIRefreshControl
实例将像以前的版本一样直接修改contentInset
。如果通过覆盖
sizeThatFits
在 UITableView 中实现自调整单元格而不使用自动布局,则返回的高度将被解释为单元格的 contentView 所需的高度,UITableViewCell 会自动添加为单元格留出空间所需的任何其他高度 分隔器。如果以这种方式实现手动自调整大小,则在 UITableViewCell 上调用sizeThatFits:
时,单元格的 contentView 宽度可以保证准确,以便在手动布局计算中使用。Trait环境(例如视图和视图控制器)现在在初始化期间使用 traits 填充 traitCollection 属性。这些初始特征表示特征环境在添加到层次结构时将接收的最终特征的预测。因为在初始化期间填充的特征只是一个预测,它们可能与实际在层次结构中接收的特征不同。因此,在可能的情况下,您应该等待执行使用 traitCollection 的工作,直到视图或视图控制器的视图移动到层次结构中 - 意味着窗口返回非零值 - 这样您就不必丢弃任何工作,如果实际特征不同,则使用预测的特征完成。使用 traitCollection 的最佳时间是在布局期间,例如 layoutSubviews,
viewWillLayoutSubviews
或viewDidLayoutSubviews
内部。只有当特征值发生变化时,才会调用
traitCollectionDidChange:
方法。重要的是,由于特征集合现在初始化为目标层次结构中最终特征的预测,当初始预测特征与层次结构中的最终特征匹配时,特征环境添加到层次结构时将不会调用traitCollectionDidChange:
。因为traitCollectionDidChange:
旨在作为无效回调来通知您一个或多个特征已更改,请审核此方法的现有实现,以及 UIContentContainer 方法willTransitionToTraitCollection:withTransitionCoordinator:
,用于您可能依赖它的地方触发初始设置。懒惰地执行使用 traitCollection 的工作的最佳位置是在上面讨论的 layoutSubviews 方法之一,但请记住,这些布局方法在任何时候布局都会被调用,所以一定要避免在不需要时重复工作。您现在可以启用调试日志记录,以便在您自己的类上调用
traitCollectionDidChange:
或willTransitionToTraitCollection:withTransitionCoordinator:
时。使用以下启动参数打开日志记录:-UITraitCollectionChangeLoggingEnabled YES。您可能希望在使用此启动参数并从 Xcode 运行应用程序时暂时禁用主线程检查程序,以避免为不相关的类添加额外的日志消息。UITableViewCell 类的 contentView 属性始终与前面和后面的相邻附件进行边对边布局。这简化了布局代码,因此想要正确的默认偏移的开发人员不再需要将其内容与内容视图边框或布局边距对齐,具体取决于尾部是否有附件。您现在应该始终在单元格内容视图的布局边距上布置代码以获取默认的系统插入。这些插入将根据单元格中可见的附件自动调整,以匹配系统的默认间距。
您现在可以从创建 block 调用自定义初始化程序,该创建块通过
instantiateInitialViewController(creator:)
或instantiateViewController(identifier:creator:)
传递。这使您可以使用其他上下文和参数初始化视图控制器,同时利用通过 Interface Builder 在故事板中定义它们。自定义控制器初始化程序必须调用其 super.init(coder:) 方法并传递它通过创建块接收的编码器参数。
网络
- 为了增强安全性,当服务器发送
Content-Type:application/octet-stream
时,NSURLSession 不再嗅探MIME
类型。 -
NSURLRequestReloadRevalidatingCacheData
和NSURLRequestReloadIgnoringLocalAndRemoteCacheData API
现已可用。 - 从 iOS 13 beta 4 开始,强制执行
NSMutableURLRequest
的HTTPBodyStream
属性的 copy 操作。如果在调用属性设置器后对 body 数据进行了修改,则 HTTP 请求中发送的数据将不包含该更变。调用该属性的 getter 不再返回 NSMutableData 引用,即使使用该类型的数据调用 setter 也是如此。从 iOS 13 beta 5 开始,使用 iOS 12 SDK 或以前的 SDK 构建的应用程序使用旧版行为。 - CNCopyCurrentNetworkInfo API 返回的信息已无法反映真实情况。有关更多详细信息,请参阅更新的API文档和标题。
- 包含 body 的 GET HTTP 方法的所有
NSURLSessionTask
实例现在都会抛出错误NSURLErrorDataLengthExceedsMaximum
。 - 删除了对代理自动配置(PAC)的 FTP 和文件URL方案的支持。HTTP 和 HTTPS 是 PAC 唯一支持的 URL 方案。这会影响所有 PAC 配置,包括但不限于使用“设置”,“系统偏好设置”,“配置文件”和
NSURLSession API
(如connectionProxyDictionary
和CFNetworkExecuteProxyAutoConfigurationURL
)设置的配置。 -
NSURLSession
和NSURLConnection
API 不再支持 SPDY。服务器应使用 HTTP 2 或 HTTP 1.1。
音频
- 现在可以在
AVAudioEngine
上启用语音处理模式。 - 新的
AVAudioNode
类型可用于包装用户定义的 block,以实时发送或接收数据。 - 基于
AVAudioEngine
的应用程序可以使用一种新方法来检索附加到AVAudioEngine
实例的所有节点的列表。 -
AVAudioEnvironmentNode
中的新渲染模式基于输出设备自动选择最佳空间音频渲染算法。 - 一个新的
AVAudioSession
属性允许在会话主动使用音频输入时播放系统声音和触觉。 - 新的枚举
AVAudioSessionPromptStyle
根据系统中的其他音频活动通知应用程序应该播放哪种语音提示。 -
AVAudioSessionRouteSharingPolicy
现在允许应用指定路由共享策略,以便其音频和视频路由到与AirPlay
相同的位置。 -
Audio Unit Extensions
现在支持所有宿主应用程序中可用的用户预设。 -
OpenAL
框架已弃用,出于兼容性目的暂时保留。过渡到 AVAudioEngine 以获得 3D 音频功能。 -
AUGraph
已被弃用,转而支持 AVAudioEngine。 - 不推荐使用应用间音频。使用
Audio Units
支持此功能。 - 不推荐使用基于
Carbon
的Audio Units
,在将来的版本中不再支持。 - 不再支持旧版
Core Audio HAL
音频硬件插件。将音频服务器插件用于支持音频驱动程序。
音频共享
- 音频共享与 AirPods(第1代或更高版本)和 PowerBeats Pro 兼容。需要 iPhone 8 或更高版本。
本文转自《iOS 13 适配 适配详解 适配要点 最全 持续补充完善》