iOS 13 如期而至,适配工作可以开展起来啦。在适配 iOS 13 过程中,遇到了如下一些问题。
1. UITextField 的私有属性 _placeholderLabel 被禁止访问了
遇到的第一个崩溃是修改UITextField
的placeholder
的颜色,历史遗留代码如下:
[_textField setValue:self.placeholderColor forKeyPath:@"_placeholderLabel.textColor"];
收到的错误信息⚠️
'Access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug'
那么这个问题如何处理呢?
其实,UITextField
有个attributedPlaceholder
的属性,我们可以自定义这个富文本来达到我们需要的结果。
修改如下:
NSMutableAttributedString *placeholderString = [[NSMutableAttributedString alloc] initWithString:placeholder attributes:@{NSForegroundColorAttributeName : self.placeholderColor}];
_textField.attributedPlaceholder = placeholderString;
注意⚠️,iOS 13 通过 KVC 方式修改私有属性,有 Crush 风险,谨慎使用!
2. 控制器的 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,
};
是的,就是UIModalPresentationAutomatic
,苹果居然直接将modalPresentationStyle
默认值改成这个,有点不解,难道是怕我们不知道新加了这个交互?这个也完全违反了开闭原则吧😒。
如何修改:
如果你完全接受苹果的这个默认效果,那就不需要去修改任何代码。
如果,你原来就比较细心,已经设置了modalPresentationStyle
的值,那你也不会有这个影响。
对于想要找回原来默认交互的同学,直接设置如下即可:
self.modalPresentationStyle = UIModalPresentationFullScreen;
值得注意的是,当 modalPresentationStyle
是 UIModalPresentationAutomatic
时,presentationController
是不会消失的。所以,关闭模态窗口的时候,presentationController
的生命周期方法 viewWillAppear:
和 viewDidAppear:
都不会触发。
3. MPMoviePlayerController 在iOS 13已经不能用了
在使用到MPMoviePlayerController
的地方,直接抛了异常:
'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
如何修改:
这个没啥好说的,既然不能再用了,那只能换掉了。替代方案就是AVKit
里面的那套播放器。
4. 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);
}
5.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:点我下载
Sign in with Apple 适配时间苹果已经确认,具体更新到了公众号同名文章,可以在文末扫码关注公众号进行查看。
6.即将废弃的 LaunchImage
从 iOS 8 的时候,苹果就引入了 LaunchScreen
,我们可以设置 LaunchScreen
来作为启动页。当然,现在你还可以使用LaunchImage
来设置启动图。不过使用LaunchImage
的话,要求我们必须提供各种屏幕尺寸的启动图,来适配各种设备,随着苹果设备尺寸越来越多,这种方式显然不够 Flexible
。而使用 LaunchScreen
的话,情况会变的很简单, LaunchScreen
是支持AutoLayout
+SizeClass
的,所以适配各种屏幕都不在话下。
注意⚠️: 从2020年4月开始,所有使⽤ iOS13 SDK
的 App
将必须提供 LaunchScreen
,LaunchImage
即将退出历史舞台。
再补充一点,在使用 LaunchScreen
的时候,里面用到的图片资源,最好别放在 xcassets
里面,不然在你修改图片后,你会发现真机上并不会生效。
7. 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
9月24更新:
关于Dark Mode
,这里补充几点。
因为苹果目前还没有强制必须适配这个,相信大家的很多项目也没有开始是配这个模式。所以,前期可以强制Light
模式。不然,你可能会遇到一些问题,比如UITableViewCell
的背景色,如果你没有设置过背景色的话,它在Dark
模式下就是黑色的,再比如UIDatePicker
文字颜色等等。
那么怎么强制模式呢?
iOS 13
给UIView
和UIViewController
都添加了一个属性:
@property (nonatomic) UIUserInterfaceStyle overrideUserInterfaceStyle API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
给这个属性设置成某一种模式,即可强制显示模式。
如果你想修改一处,应用所有地方,那么你只需要设置widow
的显示模式即可,这会影响widow下面的所有视图显示模式,这也可以看出显示模式是向下传递的。
if (@available(iOS 13, *)) {
[self.window setOverrideUserInterfaceStyle:UIUserInterfaceStyleLight];
}
8.iOS 13 UITabBar顶部分割线隐藏
这个问题源自网友的提问,我的项目中并没有这样的需求,所以之前没有处理。
看到网上的一些解决办法,如下:
[UITabBar appearance].layer.borderWidth = 0.0f;
[UITabBar appearance].clipsToBounds = YES;
看了下,实际起作用的代码是这条代码:
[UITabBar appearance].clipsToBounds = YES;
也就是裁剪掉了多余的部分(多余的部分正好就是分割线,后面会提到为什么是分割线),达到隐藏分割线的目的。但是这样设置,TabBar
会裁剪子视图,这样我们有大按钮的TabBar
,按钮就会被裁剪,造成显示不完整。
那么在iOS 13
上有没有别的办法来隐藏分割线呢?
肯定是有的,其实解决这样的问题,只要我们能找到这个视图,就可以解决问题。从这个角度出发,我们来看下iOS 13
上面TabBar
子视图都有哪些。直接打印下subviews
即可。
(
"<_UIBarBackground: 0x7fbb34007920; frame = (0 0; 414 83);",
"<UITabBarButton: 0x7fbb2ed032d0; frame = (2 1; 410 48);"
)
我们可以看到内部有个_UIBarBackground
私有的东西,分割线肯定跟他有关(总不可能跟UITabBarButton
有关吧😝)。然后,我们在看下这个控件的子视图。
(
"<UIImageView: 0x7fbb2ec09b90; frame = (0 -0.333333; 414 0.333333);",
"<UIVisualEffectView: 0x7fbb2ec05a20; frame = (0 0; 414 83); "
)
emmmm🤔,我们看到了一个越界的UIImageView
,这个UIImageView
就是分割线视图。怎么证明呢?我们加下这个代码:
[self.tabBar.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj1, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj1 isKindOfClass:[UIImageView class]]) {
obj1.hidden = YES;
}
}];
}];
运行后,发现分割线隐藏了,也就证明了我们的猜想。当然,上面这个代码不严谨哈,这里主要是提供一种思路,仅作为演示,具体怎么做相信大家都可以的。
总结下怎么解决:找到它》隐藏它。千万别通过KVC的方式去处理,理由看第一点。
9.UIWebView被废弃
"No longer supported; please adopt WKWebView.", ios(2.0, 12.0)
这个大家也尽快适配吧,看到有提到审核被拒的问题。
10.iOS 13 需增加蓝牙权限描述
简友 @funkep 提到最新一次提审被拒情况,被拒原因之一就是需要添加蓝牙权限获取描述。如果你的应用需要使用蓝牙权限,需要在Info.plist
里面加上NSBluetoothAlwaysUsageDescription
这个key
,对应的描述value
根据权限的用途来描述即可。
感谢 @funkep 的分享,大家遇到的被拒理由可以反馈到留言区,我会更新到文章里面,让更多人看到。
如果这篇文章对你有帮助,不妨随手点个赞!谢谢❤️
⚠️禁止未授权转载,只接收链接转载,不接受内容拷贝转载