随着iPhone 11的发布,iOS 13适配也提上了日程,刚好最近在做项目适配,顺便总结一下:
首先升级Xcode11,iOS13版本,因为Xcode11出现了一些新的API。废话不多说,直接上菜:
一、私有KVC
在iOS13中,不在允许通过KVC的方式去访问私有属性,需要通过其他方式去修改;之前如果使用到,会造成崩溃。
在网上看到有人说 私有KVC崩溃与系统版本无关,与Xcode版本有关,Xcode11编译会崩溃。
我个人觉得这个说法是错的;通过我的验证,我认为私有KVC在Xcode11---iOS12以下的系统不会崩溃,Xcode11---iOS13的会崩溃;大家也可以自己验证一下。
目前我找到的会触发 KVC 访问权限异常崩溃的方法有:
- UITabBarButton -> _info
- UITextField -> _placeholderLabel
- _UIBarBackground -> _shadowView
- _UIBarBackground -> _backgroundEffectView
- UISearchBar -> _cancelButtonText
- UISearchBar -> _cancelButton
- UISearchBar -> _searchField
目前我项目中,主要用到UITextField
和UISearchBar
的私有属性,现在我就拿这两个分析一下:
1、UITextFiled 修改根据kvc提示文字的大小和颜色,
这样写:
[self.onePwdField setValue:color_cccccc forKeyPath:@"_placeholderLabel.textColor"];
在Xcode11--iOS13会直接崩溃,修改方法为:
self.onePwdField.attributedPlaceholder = [self placeholder:@"请输入原来的密码"];
-(NSMutableAttributedString *)placeholder:(NSString *)text{
if (text.length == 0) {
return nil;
}
NSMutableAttributedString *att = [[NSMutableAttributedString alloc]initWithString:text attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15],NSForegroundColorAttributeName:color_cccccc}];
return att;
}
2、获取UISearchBar的textField
在iOS13之前,我们通过"_searchField"
来获取UISearchTextField来修改一些属性。
UITextField *searchFiled = [self valueForKey:@"_searchField"];
在iOS13中,继续这样会崩溃,修改方法为:
UITextField *searchFiled;
if(@available(iOS 13.0, *)) {
searchFiled = self.searchTextField;
}else{
searchFiled = [self valueForKey:@"_searchField"];
}
3、UISearchBar 黑线处理导致崩溃
iOS13之前为了处理搜索框的黑线问题,通常会遍历 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.layer.contents = nil;
break;
}
}
4、 设置UISearchBar 的searchTextField.attributedPlaceholder无效问题。
在 iOS13中需要把设置的代码写在viewDidAppear,亲测可以.
5、获取状态栏视图,以下两种方法在iOS13导致崩溃。
UIView *statusBar = [[UIApplication sharedApplication] valueForKey:@"statusBar"];
或:
UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
改为:
if(@available(iOS 13.0, *)) {
}else{
UIView *statusBar = [[UIApplication sharedApplication] valueForKey:@"statusBar"];
statusBar.transform = CGAffineTransformIdentity;
}
二、presentViewController的问题(模态弹出默认样式改变)
在 iOS 13,使用 presentViewController 方式打开视图,会跟导航栏有部分视觉差,这里就不上图了,可以自行试一下。
原因是:苹果将 UIViewController
的 modalPresentationStyle
属性的默认值改成了新加的一个枚举值 UIModalPresentationAutomatic
,对于多数 UIViewController
,此值会映射成 UIModalPresentationPageSheet
。
解决办法: 可以在vc present之前设置
modalPresentationStyle 为 UIModalPresentationFullScreen
另外,present的vc用户下拉可以
dissmiss
控制器,如果不想要这效果,可以这样设置
/*当该属性为 false 时,用户下拉可以 dismiss 控制器,为 true 时,下拉不可以 dismiss控制器*/
xxVC.isModalInPresentation = true;
- 还有一点需要注意,原来以UIModalPresentationFullScreen样式弹出页面,那么这个页面弹出 ViewController 会依次调viewWillDisappear和 viewDidDisappear。然后在这个页面被 dismiss 的时候,将他弹出的那个 ViewController 的 viewWillAppear 和 viewDidAppear会被依次调用。然而使用默认的视差效果弹出页面,将他弹出的那个 ViewController 并不会调用这些方法,原先写在这四个函数中的代码以后都有可能会存在问题。
三、蓝牙权限更新
在 iOS 13 中,苹果将原来蓝牙申请权限用的 NSBluetoothPeripheralUsageDescription 字段,替换为 NSBluetoothAlwaysUsageDescription 字段。
四、废弃UIWebview 改用 WKWebView
iOS13 开始苹果将 UIWebview 列为过期API(支持iOS2.0-iOS12)。 目前提交苹果应用市场(App Store)会发送邮件提示你在下一次提交时将应用中UIWebView 的 api 移除。
暂时没有强制使用WKWebView,但是在iOS13开始UIWebView已是废弃的API,以后更高的版本中防止出现问题,尽早移除是上上之策。
五、UISegmentedControl 默认样式改变。
默认样式变为 白底黑字,如果设置修改过颜色的话,页面需要修改。
六、WKWebView 中测量页面内容高度的方式变更
iOS 13以前 document.body.scrollHeight iOS 13中 document.documentElement.scrollHeight 两者相差55 应该是浏览器定义高度变了。
七、关于暗黑模式和切换
1、暗黑模式是iOS13的一大亮点,下面来看看模式切换的设置
(1) 切换 修改当前 UIViewController 或 UIView的模式。只要设置了控制器为暗黑模式,那么它子view也会对应的修改。
即:只会影响当前的视图,不会影响前面的 controller 和后续 present 的 controller。
if (@available(iOS 13.0, *)) {
self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;//UIUserInterfaceStyleLight
} else {
// Fallback on earlier versions
}
注意啦⚠️,但是当我们在 window 上设置 overrideUserInterfaceStyle 的时候,就会影响 window 下所有的 controller, view,包括后续推出的 controller。
(2) 获取当前的模式
if (@available(iOS 12.0, *)) {
if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ){
// Dark
NSLog(@"是dark模式、。。。");
} else if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {
// Light
NSLog(@"是light模式、。。。");
} else {
//unspecified
NSLog(@"是unspecified模式、。。。");
}
}
(3) 监听模式的变化
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
// trait发生了改变
if (@available(iOS 13.0, *)) {
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
// 执行操作
}
} else {
// Fallback on earlier versions
}
}
2、适配暗黑模式
(1)将同一个资源,创建出两种模式的样式。系统根据当前选择的样式,自动获取该样式的资源
(2)每次系统更新样式时,应用会调用当前所有存在的元素调用对应的一些重新方法,进行重绘视图,可以在对应的方法做相应的改动。
(3)资源文件适配
- 1.创建一个Assets文件(或在现有的Assets文件中)
详情请看:iOS中创建多个Assets.xcassets文件 - 2.新建一个图片资源文件(或者颜色资源文件、或者其他资源文件)
- 3.选中该资源文件, 打开 Xcode ->View ->Inspectors ->Show Attributes Inspectors (或者Option+Command+4)视图,将Apperances 选项 改为Any,Dark
- 4.执行完第三步,资源文件将会有多个容器框,分别为 Any Apperance 和 Dark Apperance. Any Apperance 应用于默认情况(Unspecified)与高亮情况(Light), Dark Apperance 应用于暗黑模式(Dark)
- 5.代码默认执行时,就可以正常通过名字使用了,系统会根据当前模式自动获取对应的资源文件
注意啦⚠️,同一工程内多个Assets文件在打包后,就会生成一个Assets.car 文件,所以要保证Assets内资源文件的名字不能相同.
-
步骤如图:
3、全局关闭黑暗模式
-
方式一 配置plist文件: 在Info.plist 文件中,添加UIUserInterfaceStyle key 名字为 User Interface Style 值为String,将UIUserInterfaceStyle key 的值设置为 Light。
在开发中,如果用的系统控件(如cell、tableview的背景色)未设置背景色(或者为透明),则进入暗黑模式后,控件背景色变为黑色。
可以每一个页面设置,当然也可以整体设置, 一般我们的APP都是在一个window下的,那就整体设置APP里的window
- 方式二 :代码关闭黑暗模式 强制关闭暗黑模式
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
if(@available(iOS 13.0,*)){
self.window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}
#endif
4、单个界面不遵循暗黑模式
- UIViewController与UIView 都新增一个属性 overrideUserInterfaceStyle
- 将 overrideUserInterfaceStyle 设置为对应的模式,则强制限制该元素与其子元素以设置的模式进行展示,不跟随系统模式改变进行改变
1、设置 ViewController 的该属性, 将会影响视图控制器的视图和子视图控制器采用该样式。
2、设置 View 的该属性, 将会影响视图及其所有子视图采用该样式。
3、设置 Window 的该属性, 将会影响窗口中的所有内容都采用样式,包括根视图控制器和在该窗口中显示内容的所有演示控制器(UIPresentationController)
更详细的暗黑模式设置请看:iOS适配暗黑模式
八、即将废弃的 LaunchImage(iOS 7.0–13.0)
从 iOS 8 的时候,苹果就引入了 LaunchScreen.storyboard,我们可以设置 LaunchScreen来作为启动页。当然,现在你还可以使用LaunchImage来设置启动图。不过使用LaunchImage的话,要求我们必须提供各种屏幕尺寸的启动图,来适配各种设备,随着苹果设备尺寸越来越多,这种方式显然不够 Flexible。而使用 LaunchScreen的话,情况会变的很简单, LaunchScreen是支持AutoLayout+SizeClass的,所以适配各种屏幕都不在话下。
注意啦⚠️,从2020年4月开始,所有使⽤ iOS13 SDK 的 App 将必须提供 LaunchScreen.storyboard,LaunchImage即将退出历史舞台,否则将无法提交到 App Store 进行审批。
九、StatusBar 与之前版本不同
目前状态栏也增加了一种模式,由之前的两种,变成了三种, 其中default由之前的黑色内容,变成了会根据系统模式,自动选择当前展示lightContent还是darkContent。
十、iOS13 获取window适配
UIWindow* window = nil;
if (@available(iOS 13.0, *)) {
for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes)
{
if (windowScene.activationState == UISceneActivationStateForegroundActive)
{
window = windowScene.windows.firstObject;
break;
}
}
}else{
window = [UIApplication sharedApplication].keyWindow;
}