暗黑模式(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.上策
- 颜色自定义:
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;
可以统一写成一个颜色的分类,方便使用。
- 图片
打开 Assets.xcassets 的,选择一个 Image set,右侧工具栏,找到Appearances,选择Any,Dark 模式,拖入不同模式下的图片资源,就大功告成了,so easy。
- 获取当前模式
//通过调用 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 类型的数据,不用自己去处理😏
弃用属性😭
UIWebview现在只是警告,还能上线,估计哪天就给弄死了,我的项目全部要改😭MPMoviePlayerController直接被禁止,使用AVPlayer解决LaunchImage直接被禁止- 部分私有方法KVC访问崩溃(估计苹果爸爸怒了,我的是私有的,你还修改个鸡儿)
- 增加苹果登录(可选)
- 模态弹出默认样式改变
- 使用
UISearchDisplayController导致崩溃,使用UISearchController解决 - 蓝牙权限字段更新导致崩溃以及提交审核失败
- 还有好多小东西,这里就不一一的列出来了,详细的请查看我参考的大佬们写的(苹果爸爸还给带来了bug 大礼包😭)