前言
随着iOS系统的更新迭代,作为iOS开发者也应该跟随其脚步, 本篇文章出于开发者的角度,量身打造,绕过新系统的一些坑,为了让我们的应用能在新的系统上流畅顺利的运行,就需要对新系统进行全方位的适配!下面我将重点挑一些已知适配的案例。
一、iOS13的变化
1.KVC
作为开发者,当你升级到Xcode11以后,你会发现你的原有项目或多或少会存在一些Crash,原因可能是私有KVC的问题,在iOS 13上不允许使用:valueForKey、setValue: forKey获取和设置私有属性,需要使用其它方式修改,不然会奔溃 ,例如常见的UITextField修改占位文字大小:
[textField setValue:[UIColor red] forKeyPath:@"_placeholderLabel.textColor"];
//UITextField有个attributedPlaceholder的属性,我们可以自定义这个富文本来达到我们需要的结果,替换为
textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"输入"attributes:@{NSForegroundColorAttributeName: [UIColor red]}];
⚠️⚠️⚠️ iOS 13 通过 KVC 方式修改私有属性,有 Crash 风险,谨慎使用!并不是所有KVC都会Crash,要尝试!
2.模态弹出界面的Type
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.
iOS 13 的 presentViewController 默认有视差效果,模态出来的界面现在默认都下滑返回。 ⚠️一些页面必须要点确认才能消失的,需要适配。如果项目中页面高度全部是屏幕尺寸,那么多出来的导航高度会出现问题。
控制器的 modalPresentationStyle 默认值变了:
查阅了下 UIModalPresentationStyle枚举定义,赫然发现iOS 13新加了一个枚举值:
typedef NS_ENUM(NSInteger, UIModalPresentationStyle) {
UIModalPresentationFullScreen = 0,
UIModalPresentationPageSheet API_AVAILABLE(ios(3.2)) API_UNAVAILABLE(tvos),
UIModalPresentationFormSheet API_AVAILABLE(ios(3.2)) API_UNAVAILABLE(tvos),
UIModalPresentationCurrentContext API_AVAILABLE(ios(3.2)),
UIModalPresentationCustom API_AVAILABLE(ios(7.0)),
UIModalPresentationOverFullScreen API_AVAILABLE(ios(8.0)),
UIModalPresentationOverCurrentContext API_AVAILABLE(ios(8.0)),
UIModalPresentationPopover API_AVAILABLE(ios(8.0)) API_UNAVAILABLE(tvos),
UIModalPresentationBlurOverFullScreen API_AVAILABLE(tvos(11.0)) API_UNAVAILABLE(ios) API_UNAVAILABLE(watchos),
UIModalPresentationNone API_AVAILABLE(ios(7.0)) = -1,
UIModalPresentationAutomatic API_AVAILABLE(ios(13.0)) = -2,
};
如果你完全接受苹果的这个默认效果,那就不需要去修改任何代码。
如果,你原来就比较细心,已经设置了modalPresentationStyle的值,那你也不会有这个影响。
对于想要找回原来默认交互的同学,直接设置如下即可:
self.modalPresentationStyle = UIModalPresentationOverFullScreen; // 全屏显示 这个不影响你的之前的界面适配
3.UISearchBar显示问题
黑线处理crash,之前为了处理搜索框的黑线问题会遍历后删除UISearchBarBackground,在iOS13会导致UI渲染失败crash;解决办法是设置UISearchBarBackground的layer.contents为nil
for (UIView *views in self.subviews.lastObject.subviews) {
if ([views isKindOfClass:NSClassFromString(@"UISearchBarBackground") ]) {
views.backgroundColor = UIColor.whiteColor;
views.layer.contents = nil;
}
}
4.MPMoviePlayerController 在iOS 13已经不能用了,既然不能用了,就只能使用其他播放器了
'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
'MPMoviePlayerController不再可用。 在AVKit中使用AVPlayerViewController。”
5.提供第三方登录的问题
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.
与Apple的登录将于今年夏天进行Beta测试。 对于支持第三方登录的应用程序的用户,今年晚些时候将提供此选项。
如果 APP 支持三方登陆(Facbook、Google、微信、QQ、支付宝等),就必须支持苹果登陆,且要放前边。关于苹果登录的,大家可以自行百度: ASAuthorizationAppleIDButton这个关键词,建议支持使用Apple提供的按钮样式,已经适配各类设备。个人理解苹果的大概意思应该是,使用第三方登录的应用,都要提供苹果登录这个模式,如果不提供,那么在审核的时候,审核应该不给通过。
6.即将废弃的 LaunchImage
从 iOS 8 的时候,苹果就引入了 LaunchScreen,我们可以设置 LaunchScreen来作为启动页。当然,现在你还可以使用LaunchImage来设置启动图。不过使用LaunchImage的话,要求我们必须提供各种屏幕尺寸的启动图,来适配各种设备,随着苹果设备尺寸越来越多,这种方式显然不够 Flexible。而使用 LaunchScreen的话,情况会变的很简单, LaunchScreen是支持AutoLayout+SizeClass的,所以适配各种屏幕都不在话下。
注意啦⚠️,从2020年4月开始,所有使⽤ iOS13 SDK的 App将必须提供 LaunchScreen,LaunchImage即将退出历史舞台。
5.Status Bar(状态栏)更新
iOS13对Status BarAPI做了修改:
之前Status Bar有两种状态:
UIStatusBarStyleDefault 文字黑色
UIStatusBarStyleLightContent 文字白色
之后Status Bar有三种状态:
UIStatusBarStyleDefault自动选择黑色或白色
UIStatusBarStyleDarkContent文字黑色
UIStatusBarStyleLightContent文字白色
7.UIActivityIndicatorView加载视图
iOS13对UIActivityIndicatorView的样式也做了修改
之前有三种样式:
UIActivityIndicatorViewStyleGray 灰色
UIActivityIndicatorViewStyleWhite 白色
UIActivityIndicatorViewStyleWhiteLarge 白色(大型)
iOS13废弃了以上三种样式,而用以下两种样式代替:
UIActivityIndicatorViewStyleLarge (大型)
UIActivityIndicatorViewStyleMedium (中型)
iOS13通过color属性设置其颜色
二、适配“暗黑模式”(Dark Mode)
好了,通过了解以上的一些注意点后,我们再来看看,在iOS 13系统新增的最重大的功能,就是黑暗模式,苹果自己叫深色模式:Dark Mode
Apps on iOS 13 are expected to support dark mode Use system colors and materials Create your own dynamic colors and images Leverage flexible infrastructure
Apps on iOS 13 are expected to support dark mode Use system colors and materials
查看了网上有些人说,iOS 13系统上,必须要适配黑暗模式,不然审核是通不过的,但苹果的说明大概意思是,这个不是强制要求,开发者可以自己选择性的上架。
1、适配Dark Mode:颜色适配,图片适配
2、获取当前模式(Light or Dark)
3、其他内容
4、总结
1、适配Dark Mode:颜色适配,图片适配
开发者主要从颜色和图片两个方面进行适配,我们不需要关心切换模式时该如何操作,这些都由系统帮我们实现
1-1、 颜色适配:
iOS13 之前 UIColor只能表示一种颜色,而从 iOS13 开始UIColor是一个动态的颜色,在Light Mode和Dark Mode可以分别设置不同的颜色。
iOS13系统提供了一些动态颜色
@property (class, nonatomic, readonly) UIColor *systemBrownColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemIndigoColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@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);
@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);
@property (class, nonatomic, readonly) UIColor *linkColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *placeholderTextColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *separatorColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *opaqueSeparatorColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *quaternarySystemFillColor
举个例子:
[self.view setBackgroundColor:[UIColor systemBackgroundColor]];
[self.titleLabel setTextColor:[UIColor labelColor]];
[self.detailLabel setTextColor:[UIColor placeholderTextColor]];
效果
用法和iOS13之前的一样,使用系统提供的这些动态颜色,不需要其他的适配操作
自定义动态UIColor
在实际开发过程,系统提供的这些颜色还远远不够,因此我们需要创建更多的动态颜色
初始化动态UIColor方法
iOS13 UIColor增加了两个初始化方法,使用以下方法可以创建动态UIColor
注:一个是类方法,一个是实例方法
+ (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);
以上这两个方法要求传一个block进去
当系统在LightMode和DarkMode之间相互切换时就会触发此回调
这个block会返回一个UITraitCollection类
我们需要使用其属性userInterfaceStyle,它是一个枚举类型,会告诉我们当前是LightMode还是DarkMode
typedef NS_ENUM(NSInteger, UIUserInterfaceStyle) {
UIUserInterfaceStyleUnspecified,
UIUserInterfaceStyleLight,
UIUserInterfaceStyleDark,
} API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);
举个例子
UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
return [UIColor redColor];
}
else {
return [UIColor greenColor];
}
}];
[self.bgView setBackgroundColor:dyColor];
1-2、适配图片
首先选择打开项目选择Assets.xcassets,随便点击一个图片素材
然后在Xcode 最右侧可以看到一个这样的选项,打开右侧工具栏,点击最后一栏,找到Appearances,选择Any,Dark
最后拖入2种模式下的图片 进去,需要UI切图
大功告成,完成颜色和图片的Dark Mode适配,是不是很easy呢!
2、 获取当前的模式
if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
[self.titleLabel setText:@"DarkMode"];
}
else {
[self.titleLabel setText:@"LightMode"];
}
3.其他
监听模式切换
有时我们需要监听系统模式的变化,并作出响应
那么我们就需要在需要监听的viewController中,重写下列函数
/ 注意:参数为变化前的traitCollection
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;
// 判断两个UITraitCollection对象是否不同
- (BOOL)hasDifferentColorAppearanceComparedToTraitCollection:(UITraitCollection *)traitCollection;
例如
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
// trait发生了改变
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
// 执行操作
}
}
强行设置App模式
当系统设置为Light Mode时,对某些App的个别页面希望一直显示Dark Mode下的样式,这个时候就需要强行设置当前ViewController的模式了
// 设置当前view或viewCongtroller的模式
@property(nonatomic) UIUserInterfaceStyle overrideUserInterfaceStyle;
在控制器中使用
// 设置为Dark Mode即可
[self setOverrideUserInterfaceStyle:UIUserInterfaceStyleDark];
⚠️ 注意!!!
当我们强行设置当前viewController为Dark Mode后,这个viewController下的view都是Dark Mode
由这个ViewController present出的ViewController不会受到影响,依然跟随系统的模式,
不想适配暗黑模式可以关闭暗黑模式:
在Info.plist文件中添加Key:User Interface Style,值类型设置为String,值为Light,就可以不管在什么模式下,软件只支持浅色模式,不支持黑暗模式,如果只想软件支持黑暗模式,则可以把类型设置为:Dark