iOS 13 适配

772 阅读4分钟

暗黑模式(Dark mode)

上策:完全的出2套UI(全部自定义)
中策:使用2套UI(尽可能多的使用系统提供的动态颜色)
下策:放弃dark 模式,只有原来的一套布局

1.下策:

直接在 Info.plist 中添加一栏:User Interface Style : Light,即可在应用内禁用暗黑模式。不过即使设置了颜色方案,申请权限的系统弹窗还是会依据系统的颜色进行显示,自己创建的 UIAlertController 就不会。

2.中策:

iOS13 之前 UIColor只能表示一种颜色,而从 iOS13 开始UIColor是一个动态的颜色,在Light Mode和Dark Mode可以分别设置不同的颜色。

下边是iOS 13 新增加的动态颜色

/* The numbered variations, systemGray2 through systemGray6, are grays which increasingly
 * trend away from systemGray and in the direction of systemBackgroundColor.
 *
 * In UIUserInterfaceStyleLight: systemGray1 is slightly lighter than systemGray.
 *                               systemGray2 is lighter than that, and so on.
 * In UIUserInterfaceStyleDark:  systemGray1 is slightly darker than systemGray.
 *                               systemGray2 is darker than that, and so on.
 */
@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);

/* Foreground color for standard system links.
 */
@property (class, nonatomic, readonly) UIColor *linkColor               API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

/* Foreground color for placeholder text in controls or text fields or text views.
 */
@property (class, nonatomic, readonly) UIColor *placeholderTextColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);

/* Foreground colors for separators (thin border or divider lines).
 * `separatorColor` may be partially transparent, so it can go on top of any content.
 * `opaqueSeparatorColor` is intended to look similar, but is guaranteed to be opaque, so it will
 * completely cover anything behind it. Depending on the situation, you may need one or the other.
 */
@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);

3.上策

  1. 颜色自定义:

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);

创建一个动态的 UIColor,如下

typedef NS_ENUM(NSInteger, UIUserInterfaceStyle) {
    UIUserInterfaceStyleUnspecified,
    UIUserInterfaceStyleLight,
    UIUserInterfaceStyleDark,
} API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);

//当系统在LightMode和DarkMode之间相互切换时就会触发此回调
UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
        if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
            return [UIColor redColor];
        }
        else {
            return [UIColor greenColor];
        }
    }];
    

CGColor依然只能表示一种颜色,只能通过UIColor转化

 UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
        if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
            return [UIColor redColor];
        }
        else {
            return [UIColor greenColor];
        }
    }];
 layer.backgroundColor = dyColor.CGColor;

可以统一写成一个颜色的分类,方便使用。

  1. 图片

打开 Assets.xcassets 的,选择一个 Image set,右侧工具栏,找到Appearances,选择Any,Dark 模式,拖入不同模式下的图片资源,就大功告成了,so easy。

  1. 获取当前模式
//通过调用 UITraitCollection.currentTraitCollection.userInterfaceStyle 获取当前模式
//只能回去当前的模式,改变以后,不能获取到
if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
    [self.titleLabel setText:@"DarkMode"];
}else {
    [self.titleLabel setText:@"LightMode"];
}

//如果想要及时知道当前模式,只能监听模式切换了
// 注意:参数为变化前的traitCollection
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;
// 判断两个UITraitCollection对象是否不同
- (BOOL)hasDifferentColorAppearanceComparedToTraitCollection:(UITraitCollection *)traitCollection;
//例如
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
    [super traitCollectionDidChange:previousTraitCollection];
    // trait发生了改变
    if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
    // 执行操作
    }
}

推送的 deviceToken 获取到的格式发生变化

原本可以直接将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 } 

解决方案:
需要进行一次数据格式处理,参考友盟的做法,可以适配新旧系统,获取方式如下:

- (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);
}

这种硬编码的方法不太好,这里推荐下边这种方法

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    if (!deviceToken || ![deviceToken isKindOfClass:[NSData class]] || deviceToken.length==0) {
        return;
    }
    NSString *(^getDeviceToken)(void) = ^() {
        if (@available(iOS 13.0, *)) {
            const unsigned char *dataBuffer = (const unsigned char *)deviceToken.bytes;
            NSMutableString *myToken  = [NSMutableString stringWithCapacity:(deviceToken.length * 2)];
            for (int i = 0; i < deviceToken.length; i++) {
                [myToken appendFormat:@"%02x", dataBuffer[i]];
            }
            return (NSString *)[myToken copy];
        } else {
            NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"<>"];
            NSString *myToken = [[deviceToken description] stringByTrimmingCharactersInSet:characterSet];
            return [myToken stringByReplacingOccurrencesOfString:@" " withString:@""];
        }
    };
    NSString *myToken = getDeviceToken();
    NSLog(@"%@", myToken);
}

我使用的极光,极光直接接收data 类型的数据,不用自己去处理😏

弃用属性😭

  1. UIWebview现在只是警告,还能上线,估计哪天就给弄死了,我的项目全部要改😭
  2. MPMoviePlayerController直接被禁止,使用AVPlayer解决
  3. LaunchImage直接被禁止
  4. 部分私有方法KVC访问崩溃(估计苹果爸爸怒了,我的是私有的,你还修改个鸡儿)
  5. 增加苹果登录(可选)
  6. 模态弹出默认样式改变
  7. 使用UISearchDisplayController导致崩溃,使用UISearchController解决
  8. 蓝牙权限字段更新导致崩溃以及提交审核失败
  9. 还有好多小东西,这里就不一一的列出来了,详细的请查看我参考的大佬们写的(苹果爸爸还给带来了bug 大礼包😭)

参考地址:
DarkModel适配
iOS 13 问题解决以及苹果登录,暗黑模式
iOS 13 适配要点总结