推荐文章
iOS Swift 自定义导航栏 FLNavigationBar 解决所有问题
新特性适配
1. 第三方登录
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. [原文]
如果 APP 支持三方登陆(Facbook、Google、微信、QQ、支付宝等),就必须支持苹果登录,且要放前边.
2. 黑暗模式
通过UITraitCollection.currentTraitCollection.userInterfaceStyle
获取当前模式。
iOS 13提供了内置的动态Color,会根据当前userInterfaceStyle自动显示不同的颜色。
UIColor
systemFillColor
UIColor
secondarySystemFillColor
UIColor
tertiarySystemFillColor
UIColor
quaternarySystemFillColor
...
// 更多的时候需要自定义动态颜色:
(UIColor )colorWithDynamicProvider:(UIColor (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
(UIColor )initWithDynamicProvider:(UIColor (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
//在回调中根据不同的userInterfaceStyle返回不同的颜色。
Web 同样需要适配黑暗模式
Web Content适配
APP 适配教程
Adopting iOS Dark Mode
iOS13 Dark Mode 适配
WWDC2019-214-iOS 13 适配 dark mode
WWDC 设计分会:iOS 13 设计新特性(1)
WWDC 设计分会:iOS 13 设计新特性(2)
Api 变化
1. KVC 限制
iOS13 以后已经不能肆无忌惮的通过 KVC 来修改一些没有暴露出来的属性了
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Access to xxx's _xxx ivar is prohibited. This is an application bug'
// UITextField 的 _placeholderLabel
[textField setValue:[UIColor xxx] forKeyPath:@"_placeholderLabel.textColor"];
// UISearchBar 的 _searchField
[searchBar valueForKey:@"_searchField"];
2. DeviceToken有变化
[deviceToken description] 获取到的格式发生变化
// objc
NSString *dt = [deviceToken description];
dt = [dt stringByReplacingOccurrencesOfString: @"<" withString: @""];
dt = [dt stringByReplacingOccurrencesOfString: @">" withString: @""];
dt = [dt stringByReplacingOccurrencesOfString: @" " withString: @""];
// swift
let dt = deviceToken.description.replacingOccurrences(of:"<", with:"").replacingOccurrences(of:">", with:"").replacingOccurrences(of:" ", with:"")
解决方案
// objc
NSMutableString *dt= [NSMutableString string];
const char *bytes = deviceToken.bytes;
NSInteger count = deviceToken.length;
for (int i = 0; i < count; i++) {
[dt appendFormat:@"%02x", bytes[i]&0x000000FF];
}
// swift
var dt: String = ""
let bytes = [UInt8](deviceToken)
for item in bytes {
dt += String(format:"%02x", item&0x000000FF)
}
3. CNCopyCurrentNetworkInfo 变化
An app that fails to meet any of the above requirements receives the following return value:
- An app linked against iOS 12 or earlier receives a dictionary with pseudo-values. In this case, the SSID is Wi-Fi (or WLAN in the China region), and the BSSID is 00:00:00:00:00:00.
- An app linked against iOS 13 or later receives NULL.
iOS13 以后只有开启了 Access WiFi Information capability,才能获取到 SSID 和 BSSID
参考:CNCopyCurrentNetworkInfo
4. 蓝牙权限说明
// 在iOS 13之前,想获取手机连接的蓝牙设备信息,可以直接使用此方法, 并不需要声明权限
[CBCentralManager retrieveConnectedPeripheralsWithServices:]
iOS 13之后,必须在Info.plist文件中增加NSBluetoothAlwaysUsageDescription
说明,否则crash。
视图适配
- 之前标记为 API_DEPRECATED 部分类被移除
1. 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.'
另外说一下,在 iOS 13 中终于可以获取直接获取搜索的文本框:
_searchBar.searchTextField.text = @"search";
2. 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
。
3. 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 进行审批。
4. 模态弹出默认交互改变
/*
Defines the presentation style that will be used for this view controller when it is presented modally. Set this property on the view controller to be presented, not the presenter.
If this property has been set to UIModalPresentationAutomatic, reading it will always return a concrete presentation style. By default UIViewController resolves UIModalPresentationAutomatic to UIModalPresentationPageSheet, but other system-provided view controllers may resolve UIModalPresentationAutomatic to other concrete presentation styles.
Defaults to UIModalPresentationAutomatic on iOS starting in iOS 13.0, and UIModalPresentationFullScreen on previous versions. Defaults to UIModalPresentationFullScreen on all other platforms.
*/
@property(nonatomic,assign) UIModalPresentationStyle modalPresentationStyle API_AVAILABLE(ios(3.2));
iOS 13 的 presentViewController 默认有视差效果,模态出来的界面现在默认都下滑返回。 一些页面必须要点确认才能消失的,需要适配。如果项目中页面高度全部是屏幕尺寸,那么多出来的导航高度会出现问题。
// Swift
self.modalPresentationStyle = .fullScreen
// Objective-C
self.modalPresentationStyle = UIModalPresentationFullScreen;
这样带来了新的交互方式,下拉就可以
dismiss
控制器,实测这是个很爽的功能,体验大幅度提升,但是对我们开发者来说呢,带来了一些坑,下面让我们来看看吧。
首先 UIModalPresentationStyle
增加了一个 automatic
属性,在 iOS 13 下默认就是这个属性。系统会根据推出的控制器来选择是 pageSheet
还是 fullScreen
,比如当我们用 UIImagePickerController
推出相机是 fullScreen
,我们自己写的控制器是 pageSheet
。如果我们只想推出 fullScreen
的控制器也很简单,present 之前设置 vc.modalPresentationStyle = .fullScreen
就好了。
接下来说一下 pageSheet
的坑是什么,我们先来看下 fullScreen
的调用顺序。
再来看下 pageSheet
的调用顺序
当A控制器 present B控制器,A控制器的 viewWillDisappear
和 viewDidDisappear
不会调用,当B控制器 dismiss,A控制器的 viewWillAppear
和 viewDidAppear
也不会调用。也就是说如果你有一些逻辑是放在这4个方法中的,要么把业务逻辑换个地方,要么设置 vc.modalPresentationStyle = .fullScreen
。
另外,UIViewController
增加一个了属性 isModalInPresentation
,默认为 false,当该属性为 false 时,用户下拉可以 dismiss 控制器,为 true 时,下拉不可以 dismiss控制器。该属性可以配合有编辑功能的控制器使用,让我们来看下官方的 Demo
<figcaption></figcaption>
我们可以看到,未编辑内容时下拉可以 dismiss,编辑了内容后下拉不可以dismiss,同时弹出了一个 alert 提示用户要不要保存编辑过的内容。详细的代码大家可以去 Demo 里看,这里就简单说一下。
首先判断用户是否输入,有输入将 isModalInPresentation
改为 true。然后实现 UIAdaptivePresentationControllerDelegate
代理的 presentationControllerDidAttemptToDismiss:
方法。这个方法会在 isModalInPresentation = true
,且用户尝试下拉 dismiss 控制器时调用。最后在这个方法里弹出 alert 提示用户是否保存编辑过的内容即可。
5. UISegmentedControl 默认样式改变
默认样式变为白底黑字,如果设置修改过颜色的话,页面需要修改原本设置选中颜色的
tintColor
已经失效,新增了selectedSegmentTintColor
属性用以修改选中的颜色。
6. UITabbar 层次发生改变,无法通过设置 shadowImage去掉上面的线
7. App启动过程中,部分View可能无法实时获取到frame
可能是为了优化启动速度,App 启动过程中,部分View可能无法实时获取到正确的frame
// 只有等执行完 UIViewController 的 viewDidAppear 方法以后,才能获取到正确的值,在viewDidLoad等地方 frame Size 为 0,例如:
[[UIApplication sharedApplication] statusBarFrame];
8. UISearchBar 黑线处理导致崩溃
之前为了处理搜索框的黑线问题,通常会遍历 searchBar 的 subViews,找到并删除 UISearchBarBackground
,在 iOS13 中这么做会导致 UI 渲染失败,然后直接崩溃,崩溃信息如下:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Missing or detached view for search bar layout'
解决办法是设置 UISearchBarBackground 的 layer.contents 为 nil:
for (UIView *view in _searchBar.subviews.lastObject.subviews) {
if ([view isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) {
// [view removeFromSuperview];
view.layer.contents = nil;
break;
}
}
9. UIActivityIndicatorView
之前的 UIActivityIndicatorView
有三种 style 分别为 whiteLarge
, white
和 gray
,现在全部废弃。
增加两种 style 分别为 medium
和 large
,指示器颜色用 color
属性修改。
10. UINavigationBar
修改 layoutMargins
属性崩溃
// MARK: layoutMargins 设置
- (void)barGainASystemView {
if (@available(iOS 11.0, *)) {
if (!_systemNavigationBarContentView) {
for (UIView *subView in self.subviews) {
if ([NSStringFromClass([subView class]) isEqualToString:@"_UINavigationBarContentView"]) {
_systemNavigationBarContentView = subView;
}
}
}
if (_systemNavigationBarContentView) {
// _systemNavigationBarContentView.layoutMargins = UIEdgeInsetsZero;
}
}
}
·参考文献
本文结合以下文档内容和个人遇到的问题,对常见适配问题进行总结