阅读 1671

iOS 13 适配

1、前言

针对 iOS 13,苹果做了非常大的调整,其中 Dark 模式是最新的 UI 层面可见的改变,而针对iPad推出了 iPadOS,导致了与 iOS 的分化,同时为了 iPad 而不得不对 iOS 同样做出的改变,比如一个应用支持多 window 显示。所以,每年 WWDC 后,因为每个应用的情况不一样,使用的API各不相同,导致适配的工作无法一致,大家遇到的坑不相同,所以,把大家遇到的问题都汇总起来,那就是一定有作用,这就是本文的初心。

2、iOS 13 适配问题

1. 如何关闭程序中的暗黑模式?

iOS13最大的特点是带来了暗黑模式,但是如果不适配的话,可能会出现该是黑色的地方用了白色了,该是白色的地方是黑色了,看不到了,怎么关掉呢?

  • 1.1 代码方式: 可以每一个页面设置,当然也可以整体设置,一般我们的APP都是在一个window下的,那就整体设置APP里的window:
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
    if(@available(iOS 13.0,*)){
        self.window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
    }
#endif
复制代码
  • 1.2 配置文件方式: 在 Info.plist 添加key: UIUserInterfaceStyle 设置为强制用暗黑模式(Dark)或白天模式(Light

iOS13-Dark.png

<key>UIUserInterfaceStyle</key>
<string>UIUserInterfaceStyleLight</string>
复制代码

目前苹果还没有公布什么时候强制适配黑夜模式。按之前的官方的文章其实是没有强制要求所有App都适配,因为有些App确实可能很难适配。

如果您需要更多时间调节 app 在深色模式中的外观,或您的 app 不适用于深色模式,您可进一步了解如何停用深色模式。

2. UIWebView 已弃用

ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs . See developer.apple.com/documentati… for more information.

UIWebView iOS 2.0–12.0 Deprecated Mac Catalyst 13.0–13.0 Deprecated

2019-12月更新

The App Store will no longer accept new apps using UIWebView as of April 2020 and app updates using UIWebView as of December 2020.

苹果已经强制要求2020年4月新应用不能再使用 UIWebView,2020年12月所有应用更新不能在使用 UIWebView。

3. PresentViewController 模式变动

iOS13后,Prensent方式弹出页面时,默认的模式变为了UIModalPresentationAutomatic,这样的方式也挺好的,动画也好看,自带关闭,只要下拉就关闭页面了,这样的动画方式下,之前会出现的《UITabBar在iPhoneX等Push时显示错乱问题》 的问题也不存在了。 但有的时候,我们不希望使用这个样式,比如需要用户不能关闭的:

iOS13-PresentViewController.png

只要指定弹出页的 modalPresentationStyle 属性,注意是要Present的页面:

UIViewController *vc = [[UIViewController alloc] init];
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController presentViewController:vc animated:YES completion:nil];
复制代码

4. 增加"一直使用蓝牙"的权限申请

iOS 13起,app的 info.plist里增加

<key>NSBluetoothAlwaysUsageDescription</key>
<string>我们要一直使用您的蓝牙,具体做什么别问我</string>
复制代码

5. WKWebView 中测量页面内容高度的方式变更

iOS 13以前

document.body.scrollHeight 
复制代码

iOS 13中

document.documentElement.scrollHeight
复制代码

两者相差55 应该是浏览器定义高度变了

6. UIActivityIndicatorView

之前的 UIActivityIndicatorView 有三种 style 分别为 whiteLarge, whitegray,现在全部废弃。 增加两种 style 分别为 mediumlarge,指示器颜色用 color 属性修改。

iOS13-UIActivityIndicatorView.png

7. Status Bar

去掉原来 .default .lightContent,改为 :

![iOS13-Status Bar.png](github.com/iHTCboy/iGa… Bar.png)

8. 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. Apple登录将于今年夏天进行beta测试。对于支持第三方用户登录的应用中,apple 登录将在今年晚些时候上市销售时作为选项。

通过API来获取到诸如用户姓名这样的信息,但是最为关键的userIdentifier则是毫无规律可言的(至少在我们开发者看来),而这个userIdentifier则为同一个开发者账号下的所有app中保持有且仅有一个。 另外,apple 登录是支持跨平台,安卓和 windows 的 web 页面中使用 apple id 登录授权。

注意: Sign In with Apple 需要用户开启了两步认证,如果没有开启则会在第一次使用时提示开启,不开启将无法使用。

另外,苹果登录按钮有所要求, 详细见 Sign In with Apple - Sign In with Apple - Human Interface Guidelines - Apple Developer

2019年9月更新

4.8 通过 Apple 登录 如果 app 专门使用第三方或社交登录服务 (例如,Facebook 登录、Google 登录、通过 Twitter 登录、通过 LinkedIn 登录、通过 Amazon 登录或微信登录) 来对其进行设置或验证这个 app 的用户主帐户,则该 app 必须同时提供“通过 Apple 登录”作为等效选项。用户的主帐户是指在 app 中建立的、用于标识身份、登录和访问功能和相关服务的帐户。 在以下情况下,不要求提供“通过 Apple 登录”选项:

  • 您的 app 仅使用公司自有的帐户设置和登录系统。
  • 您的 app 是一款教育、企业或商务 app,要求用户使用现有的教育或企业帐户登录。
  • 您的 app 使用政府或行业支持的公民身份系统或电子身份证来鉴定用户身份。
  • 您的 app 是特定第三方服务的客户端,用户需要使用他们的邮件、社交媒体或其他第三方帐户直接登录才能访问内容。

9. KVC 限制

iOS 13 通过 KVC 方式修改私有属性,有 Crush 风险,谨慎使用! iOS13 以后已经不能肆无忌惮的通过 KVC 来修改一些没有暴露出来的属性了。会崩溃: 'Access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug'

已知:

// UITextField的placeholder的颜色
[textField setValue:[UIColor xxx] forKeyPath:@"_placeholderLabel.textColor"];
// UISearchBar 的 _searchField
[searchBar valueForKey:@"_searchField"];
复制代码

修改如下:

NSMutableAttributedString *placeholderString = [[NSMutableAttributedString alloc] initWithString:placeholder attributes:@{NSForegroundColorAttributeName : self.placeholderColor}];
_textField.attributedPlaceholder = placeholderString;
复制代码

10. UISegmentedControl 默认样式改变

默认样式变为白底黑字,如果设置修改过颜色的话,页面需要修改。另外UI样式发生了变化。

11. App启动过程中,部分View可能无法实时获取到frame

可能是为了优化启动速度(或者留海屏原因),App 启动过程中,部分View可能无法实时获取到正确的frame

// 只有等执行完 UIViewController 的 viewDidAppear 方法以后,才能获取到正确的值,在viewDidLoad等地方 frame Size 为 0,例如:
 [[UIApplication sharedApplication] statusBarFrame];
复制代码

12. MPMoviePlayerController 在iOS 13 弃用

在使用到MPMoviePlayerController的地方,直接抛了异常:

'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
复制代码

替代方案就是AVKit里面的那套播放器

13. 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 }
复制代码

解决方法,fb sdk 中:

+ (NSString *)hexadecimalStringFromData:(NSData *)data
{
    NSUInteger dataLength = data.length;
    if (dataLength == 0) {
        return nil;
    }
    const unsigned char *dataBuffer = data.bytes;
    NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
    for (int i = 0; i < dataLength; ++i) {
        [hexString appendFormat:@"%02x", dataBuffer[i]];
    }
    return [hexString copy];
}
复制代码

友盟推送的解决方法

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
     if (![deviceToken isKindOfClass:[NSData class]]) return;
           const unsigned *tokenBytes = (const unsigned *)[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);
     }
}

复制代码

这2个方法那个好? 根据苹果文档 application:didRegisterForRemoteNotificationsWithDeviceToken:

友盟的兼容方案并不严谨,苹果说tokens长度可变,建议不要硬编码!!

更多参考: iOS13 PKPushCredentials broken |Apple Developer Forums facebook-objc-sdk/FBSDKInternalUtility.m 【公告】iOS 13正式发布后对U-Push的影响 NotificationCenter · Issue #23 · ChenYilong/iOS13AdaptationTips

14. 即将废弃的 LaunchImage

从 iOS 8 的时候,苹果就引入了 LaunchScreen,我们可以设置 LaunchScreen来作为启动页。当然,现在你还可以使用LaunchImage来设置启动图。不过使用LaunchImage的话,要求我们必须提供各种屏幕尺寸的启动图,来适配各种设备,随着苹果设备尺寸越来越多,这种方式显然不够 Flexible。而使用 LaunchScreen的话,情况会变的很简单, LaunchScreen是支持AutoLayout+SizeClass的,所以适配各种屏幕都不在话下。

注意: 从2020年4月开始,所有使⽤ iOS13 SDK 的 App 将必须提供 LaunchScreen,LaunchImage即将退出历史舞台。

再补充一点,在使用 LaunchScreen的时候,里面用到的图片资源,最好别放在 xcassets 里面,不然在你修改图片后,你会发现真机上并不会生效。

15. App Delegate

iOS 支持多界面同时运行多个用户界面。 root view controller 的 view superview 也不直接是 UIWindow 了:

iOS13-Multiple-Windows.png

iOS 13 可以多windows窗口,把ui相关的生命周期单独到 SceneDelegate:

iOS13-SessionLifecycle.png

如果实现 SceneDelegate 那么iOS 13会调整 SceneDalegate, 如果不实现,就会使用 AppDelegate。iOS 12 以下还是调用 AppDelegate:

iOS13-UISessionDelegate.png

@property(nullable, nonatomic,readonly) UIWindow *keyWindow API_DEPRECATED("Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes", ios(2.0, 13.0));
复制代码

获取keyWindow 使用 [UIApplicatoin sharedApplication].keyWindow 情况就有多种:

  • 1、此接口已经弃用
  • 2、针对 iPad, 一个应用可以打开多窗口(比如 office 可以打开多个 word 文档。),此时,keyWindow 就有多个 scenes,并不唯一。
  • 3、针对 iPhone,目前还不能同时打开多窗口。所以 keyWindow 还是唯一的,暂时不影响获取。

没有了 keyWindow,那以后怎么获得当前激活中的 window 呢?可以看看这里:How to resolve: 'keyWindow' was deprecated in iOS 13.0

具体调整可参见官方WWDC: Architecting Your App for Multiple Windows - WWDC 2019 - Videos - Apple Developer

16. - (BOOL)application: openURL: options: 中 SourceApplication 为 nil

在 Xcode 11之前的文档:

// value is an NSString containing the bundle ID of the originating application

在 Xcode 11后变为:

// value is an NSString containing the bundle ID of the originating application; non-nil if the originating application and this application share the same team identifier

官方在线文档:

UIApplicationOpenURLOptionsSourceApplicationKey A key that contains the bundle ID of the app that sent the open-URL request to your app.

The value of this key is an NSString object containing the bundle ID of the app that made the request. If the request originated from another app belonging to your team, UIKit sets the value of this key to the ID of that app. If the team identifier of the originating app is different than the team identifier of the current app, the value of the key is nil.

在 iOS 13 中,非同一开发者账号(team identifier)的App,在 iOS 13 通过 URL Scheme 打开App里,在代理方法 - (BOOL)application: openURL: options: 中将UIApplicationOpenURLOptionsSourceApplicationKey返回为nil,也就是说,再也拿不到原App的 bundle ID,关于这个大家都说是苹果为了解决隐私问题?

对于开发者来说,如果有使用到 SourceApplication 字段,只能在 iOS 13以上调整自己的业务逻辑。

总结

这些都只是常见和汇总网上大家遇到的情况,还有很多细节上,针对 iOS13 需要大家细调,这些年下来,iOS各系统版本之前的突变很多,当然这样也有好处,比如 WKWebview 支持 iOS 8.0 + , SFSafariViewController 支持 iOS 9.0+,慢慢地,很多更好的 API 大家都可以使用,抛弃旧的接口,兼容新的接口,这样的工作,可预见未来一直会这样,谁叫我们是 API 使用者!努力成为提供 API man 吧!

参考


  • 如有不正确的地方,欢迎指导!
  • 如有疑问,欢迎在评论区一起讨论!

> 注:本文首发于 [iHTCboy's blog](https://iHTCboy.com),如若转载,请注来源
文章分类
iOS
文章标签