iOS13系统适配

iOS13适配项列表

1.私有KVO
2.presentViewController
3.UISearchBar显示问题
4.TabBar红点偏移
5.MPMoviePlayerController 在iOS 13已经不能用了
6.iOS 13 DeviceToken有变化
7.Sign in with Apple (提供第三方登录的注意啦)
8.即将废弃的 LaunchImage
9.Web暗色模式适配
10.Native暗色模式适配

1.私有KVC
        在使用iOS 13运行项目时突然APP就crash掉了。定位到的问题是在设置UITextField的Placeholder也就是占位文本的颜色和字体时使用了KVC的方法:
[_textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
[_textField setValue:[UIFont systemFontOfSize:14] forKeyPath:@"_placeholderLabel.font"];
可以将其替换为
_textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"姓名" attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14],NSForegroundColorAttributeName:[UIColor redColor]}];
并且只需要在初始化的时候设置attributedPlaceholder即富文本的占位文本,再重新赋值依然使用placeolder直接设置文本内容,样式不会改变。


2. iOS13中presentViewController的问题
        更新了Xcode11.0 beta之后,在iOS13中运行代码发现presentViewController和之前弹出的样式不一样。

会出现这种情况是主要是因为我们之前对UIViewController里面的一个属性,即modalPresentationStyle(该属性是控制器在模态视图时将要使用的样式)没有设置需要的类型。在iOS13中modalPresentationStyle的默认改为UIModalPresentationAutomatic,而在之前默认是UIModalPresentationFullScreen。
/*
 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));
要改会原来模态视图样式,我们只需要把UIModalPresentationStyle设置为UIModalPresentationFullScreen即可。
ViewController *vc = [[ViewController alloc] init];
vc.title = @"presentVC";
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
nav.modalPresentationStyle = UIModalPresentationFullScreen;
[self.window.rootViewController presentViewController:nav animated:YES completion:nil];


3. UISearchBar显示问题
        UITextField升级到iOS13,UISearchController上的SearchBar显示异常,查看后发现对应的高度只有1px,目前没找到具体导致的原因,解决办法是使用KVO监听frame值变化后设置去应该显示的高度
黑线处理crash
之前为了处理搜索框的黑线问题会遍历后删除UISearchBarBackground,在iOS13会导致UI渲染失败crash;解决办法是设置UISearchBarBackground的layer.contents为nil
-(void)clearBlackLine{
for(UIView *view in self.subviews){
if([view isKindOfClass:NSClassFromString(@”UISearchBarBackground”)]){
view.backgroundColor = [UIColor whiteColor];
view.layer.contents = nil;
break;
}
}
}


4. TabBar红点偏移
         如果之前有通过TabBar上图片位置来设置红点位置,在iOS13上会发现显示位置都在最左边去了。遍历UITabBarButton的subViews发现只有在TabBar选中状态下才能取到UITabBarSwappableImageView,解决办法是修改为通过UITabBarButton的位置来设置红点的frame


5. MPMoviePlayerController 在iOS 13已经不能用了
          在使用到MPMoviePlayerController的地方,直接抛了异常:
'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.' 
如何修改:
这个没啥好说的,既然不能再用了,那只能换掉了。替代方案就是AVKit里面的那套播放器。


6. iOS 13 DeviceToken有变化
           这个很重要,可能大多数使用第三方推送的童鞋都不会注意到这个问题,一般现在的第三方推送都是将DeviceToken原始数据丢进去,具体的解析都是第三方内部处理,所以,这些第三方解析DeviceToken的方式正确的话,那就毫无问题。如果你们是通过这种方式来获取DeviceToken,那你需要注意了。(这个坑也是多年前埋下的,很多文章介绍的也是下面这个方法,不规范的做法迟早要还的),如下:
NSString *dt = [deviceToken description];
dt = [dt stringByReplacingOccurrencesOfString: @"<" withString: @""];
dt = [dt stringByReplacingOccurrencesOfString: @">" withString: @""];
dt = [dt stringByReplacingOccurrencesOfString: @" " withString: @""];
这段代码运行在 iOS 13 上已经无法获取到准确的DeviceToken字符串了,iOS 13 通过[deviceToken description]获取到的内容已经变了。
{length = 32, bytes = 0x778a7995 29f32fb6 74ba8167 b6bddb4e ... b4d6b95f 65ac4587 }
可以看到,跟原来我们认识的那个已经完全不一样了。其实,造成这样的问题,主要还是没有使用正确的方式来操作,下面是解决办法:
NSMutableString *deviceTokenString = [NSMutableString string];
const char *bytes = deviceToken.bytes;
NSInteger count = deviceToken.length;
for (int i = 0; i < count; i++) {
    [deviceTokenString appendFormat:@"%02x", bytes[i]&0x000000FF];
}
或者你也可以使用友盟提供的方法(2019年7月24日更新)
- (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);
}

 
7. Sign in with Apple (提供第三方登录的注意啦)
          如果你的应用使用了第三方登录,那么你可能也需要加下 「Sign in with Apple」
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.
关于如何集成,可以参考这篇文章:《Sign in with Apple》。
附上官方Demo:点我下载


8. 即将废弃的 LaunchImage
         从 iOS 8 的时候,苹果就引入了 LaunchScreen,我们可以设置 LaunchScreen来作为启动页。当然,现在你还可以使用LaunchImage来设置启动图。不过使用LaunchImage的话,要求我们必须提供各种屏幕尺寸的启动图,来适配各种设备,随着苹果设备尺寸越来越多,这种方式显然不够 Flexible。而使用 LaunchScreen的话,情况会变的很简单, LaunchScreen是支持AutoLayout+SizeClass的,所以适配各种屏幕都不在话下。
注意: 从2020年4月开始,所有使⽤ iOS13 SDK 的 App 将必须提供 LaunchScreen,LaunchImage即将退出历史舞台。
再补充一点,在使用 LaunchScreen的时候,里面用到的图片资源,最好别放在 xcassets 里面,不然在你修改图片后,你会发现真机上并不会生效。


9. Web暗色模式适配
第一、Web Content适配
https://developer.apple.com/videos/play/wwdc2019/511/
https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
问题展示
先看两张图:

如上图所示,如果h5未适配dark模式,则在dark模式下原来的页面内容展示就就存在问题。
适配方法
这里主要介绍基于CSS样式的修改来适配web内容
首先,一定要声明当前支持的color-scheme有两种样式,这一句很重要,用东北话说就是“必须的”
:root {
    color-scheme: light dark;
}
适配的策略就是为两种color-scheme设置不同的颜色样式。
1、文本适配
如图一,它的CSS描述为
 body {
     color: black;
 }
 h1 {
     color: #333;
 }
 .header {
    background-color: #593a78;
    color: white;
 }
这里相关的颜色样式都是写死的,所以dark模式下才会出现图二的情况。现在我们来看如何适配下面这段代码:
h1 {
    color: #333;
}
.header {
    background-color: #593a78;
    color: white;
}
然后可用如下方式改造:
:root {
    color-scheme: light dark; 
    --post-title-color: #333;
    --header-bg-color: #593a78;
    --header-txt-color: white;
}
h1 {
    color: var(--post-title-color);
}
.header {
    background-color: var(--header-bg-color);
    color: var(--header-txt-color);
}
这里所做的工作实际上是抽象了颜色的值的setter和getter,即不同模式下的值统一定义,然后使用时通过var()去自动获取。
而结合@media与prefers-color-scheme,颜色值的定义可以更加清晰:
@media (prefers-color-scheme: dark) {
    :root {
        --post-title-color: white;
        --header-bg-color: #513d66;
        --header-txt-color: #eee;
    }
}
同理可以定义light模式下的色值。
2、图片适配
假设原文件有一幅图片资源,其CSS描述如下:

那么,可以通过下面的方式来达到适配两种模式的目的:

   
   

3、动态内容适配
动态内容适配需要注意:WebKit提供了获取指定模式下需要适配的多媒体项的能力,以及不同模式间切换的通知,一般情况下,通过下面的方式可以完成适配:
// Adapting dynamic content to dark mode with JavaScript
let darkModeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
function updateForDarkModeChange() {
    if (darkModeMediaQuery.matches) {
        ...
    } else {
        ...
    }
}
darkModeMediaQuery.addListener(updateForDarkModeChange);
updateForDarkModeChange(); // Handle if Dark Mode is already active.
所以,这里很显然要处理light2dark和dark2light等情形。   
如果需要更深度的定制,需h5与客户端评估是否需要注入js变量来判断当前模式。如果涉及服务端的直接适配,则可考虑在UA中添加识别信息。
4、小结:
使用 color-scheme 来声明支持的模式;
利用prefers-color-scheme来设置不同模式下的配置;
利用 标签设置不同模式下的图片;
利用var() 方法来适配不同模式下的变量取值;
动态内容适视具体情况而定。


10. Native暗色模式适配
        夜间模式是iOS13的重要更新之一,随之而来的是我们能从系统设置中“显示与亮度”中选择“浅色”、“深色”两种模式,并且可以设置自动切换。(“控制中心”亮度调节中也可直接调节)
已知问题:在系统设置为深色模式时候,无法更改StateBar颜色
如果不想适配深色模式 
(1).直接在项目的plist文件中设置 
UIUserInterfaceStyle UIUserInterfaceStyleLight
(2).在每个UIViewController或者BaseViewController(如果自己有的话),中设置
 if (@available(iOS 13.0, *))
{ self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight; }

如果适配深色模式 首先我们要看一下显示模式的枚举值
typedef NS_ENUM(NSInteger, UIUserInterfaceStyle) {
    UIUserInterfaceStyleUnspecified,
    UIUserInterfaceStyleLight,
    UIUserInterfaceStyleDark,
} API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);
当前API还没有提供浅色/深色模式切换时的通知,但是为UIColor添加了新方法: 
+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider; 该方法通过一个block返回颜色,根据其中UITraitCollection参数,我们可以获取到当前系统的UIUserInterfaceStyle. 这个方法会在每次系统模式改变后回调,所以我想,我可以在一个颜色中去为当前界面做监听.
Xcode 11为xcassets带来更新以自动读取加载浅色/深色模式的资源,只要修改资源Appearances属性,来设置是否要支持浅色/深色模式,以及资源内容即可,[UIImage imageNamed:@""]会自动加载浅色/深色资源.
最后最后上一段 UIViewController 代码
#import "DarkModeViewController.h"

@interface DarkModeViewController ()
{
    UIImageView *_iv2;
    UIUserInterfaceStyle _userInterfaceStyle;
}
@end

@implementation DarkModeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self)weakSelf = self;
    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];
    }];
    self.view.backgroundColor = backgroundColor;
    
    UIImageView *iv = ({
        UIImageView *imageView = [[UIImageView alloc] init];
        [imageView setBackgroundColor:[UIColor clearColor]];
        [imageView setFrame:CGRectMake(20, 100, 100, 100)];
        imageView.image = [UIImage imageNamed:@"icon_star"];
        imageView;
    });
    [self.view addSubview:iv];
    
    _iv2 = ({
        UIImageView *imageView = [[UIImageView alloc] init];
        [imageView setBackgroundColor:[UIColor clearColor]];
        [imageView setFrame:CGRectMake(20, 240, 100, 100)];
        imageView;
    });
    [self.view addSubview:_iv2];
    [self iv2updateImage];
}

- (void)traitCollectionChanged:(UITraitCollection *)traitCollection{
    NSLog(@"traitCollectionChanged:%ld",traitCollection.userInterfaceStyle);
    [self iv2updateImage];
    
}
- (void)iv2updateImage {
    NSLog(@"iv2updateImage:%ld",_userInterfaceStyle);
    if (_userInterfaceStyle == UIUserInterfaceStyleDark) {
        _iv2.image = [UIImage systemImageNamed:@"star.circle.fill"];
    }else{
        _iv2.image = [UIImage systemImageNamed:@"star.circle"];
    }
}
@end
在iOS13,为UIViewController和UIView扩展了一个新的API-overrideUserInterfaceStyle,使用方法,官方文档大致是这么说的:
通过设置overrideUserInterfaceStyle属性以使该视图及其子视图具有特定的UIUserInterfaceStyle。但如果想要获取当前的UIUserInterfaceStyle,需要改用traitCollection.userInterfaceStyle。
尽可能使用UIViewController上的overrideUserInterfaceStyle属性。仅在以下时间使用此属性:
在单个视图或小视图层次结构上局部使用特定样式。
您希望在整个UIWindow及其视图控制器和模态弹出的ViewController上使用特定样式,且不希望强制更改整个应用程序具有样式。 (如果您确实希望整个应用程序具有某种样式,请不要使用它,而是在Info.plist中设置UIUserInterfaceStyle键。)
当设置在普通的UIView上时:
此属性仅影响此视图及其子视图的特征。
它不会影响任何视图控制器或其他视图控制器的子视图。
在UIWindow上设置时:
此属性会影响rootViewController,从而影响整个视图控制器和视图层次结构。
它还会影响该window模态出来的界面。
由此可见,overrideUserInterfaceStyle不仅会影响自己,还会影响自己的子视图,换做window就会影响整个window中的所有视图及视图控制器,包括模态跳转出来的视图控制器。
而且,文档中也特别强调了,你可以设置整个应用程序只是用某种样式,具体方法可以通过代码,也可以通过info.plist配置键User Interface Style,对应的Value为Light/Dark。
if (@available(iOS 13.0, *))
{ window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight; }
 
适配主要涉及的方面:
模拟器调试(simulator debug)
图片(assets)
颜色(color)
状态栏(status bar)

模拟器调试
运行项目
点击Xcode底部调试栏中Environment Overrides 

开启Interface Style,就可以切换了。
图片适配
图片适配,主要是我们本地图片资源适配,网络图片的话,还是比较繁琐的,目前我们还没做,只做下本地图片适配。
图片适配比较方便的就是通过Assets.xcassets进行图片管理:
添加一个image set,重命名如"adaptimage",选中该image set;
选中Attributes Inspector;
将Appearances由"None"改为"Any,Dark";
不同模式下设置不同图片即可,mode 改变会自动选择不同的图片

当然图片适配,你也可以直接使用判断当前系统mode的方式进行区分,就我个人而言不是很喜欢这种方式,因为还需要监听系统模式的变化,重写UITraitEnvironment协议方法traitCollectionDidChange(_:),我们先看下协议方法:
/** Trait environments expose a trait collection that describes their environment. */
@protocol UITraitEnvironment
@property (nonatomic, readonly) UITraitCollection *traitCollection NS_AVAILABLE_IOS(8_0);

/*! To be overridden as needed to provide custom behavior when the environment's traits change. */
- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection NS_AVAILABLE_IOS(8_0);
@end
所以,我们只需要在改变系统mode的时候,重写代理:
- (void)traitCollectionChanged:(UITraitCollection *)traitCollection{
    NSLog(@"traitCollectionChanged:%ld",traitCollection.userInterfaceStyle);
    [self iv2updateImage];
    
}
- (void)iv2updateImage {
    NSLog(@"iv2updateImage:%ld",_userInterfaceStyle);
    if (_traitCollection.userInterfaceStyle
  == UIUserInterfaceStyleDark) {
        _iv2.image = [UIImage systemImageNamed:@"star.circle.fill"];
    }else{
        _iv2.image = [UIImage systemImageNamed:@"star.circle"];
    }
}
颜色适配
像图片适配一样,颜色适配有三种方式。
方法一:是通过Assets.xcassets添加一个Color Set,目前系统支持≥iOS11.0
@interface UIColor (UIColorNamedColors)
+ (nullable UIColor *)colorNamed:(NSString *)name NS_AVAILABLE_IOS(11_0);      // load from main bundle
+ (nullable UIColor *)colorNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection NS_AVAILABLE_IOS(11_0);
@end
 
方法二:代码创建动态颜色init(dynamicProvider: @escaping (UITraitCollection) -> UIColor),目前系统支持≥iOS 13.0
// 方法二
if (@available(iOS 13.0, *)) {
        UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
            if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {
                return lightColor;
            }else {
                return darkColor;
            }
        }];
        return dyColor;
    }else{
        return lightColor;
    }
方法三:像图片一样,监听模式转变,重写traitCollectionDidChange(_:)方法,不推荐
状态栏(StatusBar)
目前状态栏也增加了一种模式,由之前的两种,变成了三种, 其中default由之前的黑色内容,变成了会根据系统模式,自动选择当前展示lightContent还是darkContent。
typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
    UIStatusBarStyleDefault = 0, // Dark content, for use on light backgrounds
    UIStatusBarStyleLightContent NS_ENUM_AVAILABLE_IOS(7_0) = 1, // Light content, for use on dark 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,
} __TVOS_PROHIBITED;
我们在使用的时候,就可以重写preferredStatusBarStyle的get方法:
- (UIStatusBarStyle)preferredStatusBarStyle {    
      return UIStatusBarStyleLightContent;
}

你可能感兴趣的:(iOS13系统适配)