iOS13适配

随着iPhone 11的发布,iOS 13适配也提上了日程,刚好最近在做项目适配,顺便总结一下:
首先升级Xcode11,iOS13版本,因为Xcode11出现了一些新的API。废话不多说,直接上菜:

一、私有KVC

在iOS13中,不在允许通过KVC的方式去访问私有属性,需要通过其他方式去修改;之前如果使用到,会造成崩溃。

在网上看到有人说 私有KVC崩溃与系统版本无关,与Xcode版本有关,Xcode11编译会崩溃。
我个人觉得这个说法是错的;通过我的验证,我认为私有KVC在Xcode11---iOS12以下的系统不会崩溃,Xcode11---iOS13的会崩溃;大家也可以自己验证一下。

目前我找到的会触发 KVC 访问权限异常崩溃的方法有:

  • UITabBarButton -> _info
  • UITextField -> _placeholderLabel
  • _UIBarBackground -> _shadowView
  • _UIBarBackground -> _backgroundEffectView
  • UISearchBar -> _cancelButtonText
  • UISearchBar -> _cancelButton
  • UISearchBar -> _searchField

目前我项目中,主要用到UITextFieldUISearchBar的私有属性,现在我就拿这两个分析一下:

1、UITextFiled 修改根据kvc提示文字的大小和颜色,

这样写:

[self.onePwdField setValue:color_cccccc forKeyPath:@"_placeholderLabel.textColor"];

在Xcode11--iOS13会直接崩溃,修改方法为:

self.onePwdField.attributedPlaceholder = [self placeholder:@"请输入原来的密码"];

-(NSMutableAttributedString *)placeholder:(NSString *)text{
    if (text.length == 0) {
        return nil;
    }
    NSMutableAttributedString *att = [[NSMutableAttributedString alloc]initWithString:text attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15],NSForegroundColorAttributeName:color_cccccc}];
    return att;
}
2、获取UISearchBar的textField

在iOS13之前,我们通过"_searchField"来获取UISearchTextField来修改一些属性。

 UITextField *searchFiled = [self valueForKey:@"_searchField"];

在iOS13中,继续这样会崩溃,修改方法为:

 UITextField *searchFiled;
 if(@available(iOS 13.0, *)) {
    searchFiled =  self.searchTextField; 
 }else{
    searchFiled = [self valueForKey:@"_searchField"];
 }
3、UISearchBar 黑线处理导致崩溃

iOS13之前为了处理搜索框的黑线问题,通常会遍历 searchBar 的 subViews,找到并删除 UISearchBarBackground
在 iOS13 中这么做会导致 UI 渲染失败,然后直接崩溃,崩溃信息如下:

Terminating app due to uncaught exception'NSInternalInconsistencyException', reason: 'Missing or detached view for search bar layout'

修改方法为:设置 UISearchBarBackground 的 layer.contents 为 nil

 for (UIView *view in _searchBar.subviews.lastObject.subviews) {
   if ([view isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) {
        view.layer.contents = nil;
        break;
    }
 } 
4、 设置UISearchBar 的searchTextField.attributedPlaceholder无效问题。

在 iOS13中需要把设置的代码写在viewDidAppear,亲测可以.

5、获取状态栏视图,以下两种方法在iOS13导致崩溃。
UIView *statusBar = [[UIApplication sharedApplication] valueForKey:@"statusBar"];
或:
UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];

改为:

if(@available(iOS 13.0, *)) {
        
}else{
    UIView *statusBar = [[UIApplication sharedApplication] valueForKey:@"statusBar"];
    statusBar.transform = CGAffineTransformIdentity;
}

二、presentViewController的问题(模态弹出默认样式改变)

在 iOS 13,使用 presentViewController 方式打开视图,会跟导航栏有部分视觉差,这里就不上图了,可以自行试一下。
原因是:苹果将 UIViewControllermodalPresentationStyle 属性的默认值改成了新加的一个枚举值 UIModalPresentationAutomatic,对于多数 UIViewController,此值会映射成 UIModalPresentationPageSheet

  • 解决办法: 可以在vc present之前设置modalPresentationStyle 为 UIModalPresentationFullScreen

  • 另外,present的vc用户下拉可以dissmiss控制器,如果不想要这效果,可以这样设置

/*当该属性为 false 时,用户下拉可以 dismiss 控制器,为 true 时,下拉不可以 dismiss控制器*/

xxVC.isModalInPresentation = true;
  • 还有一点需要注意,原来以UIModalPresentationFullScreen样式弹出页面,那么这个页面弹出 ViewController 会依次调viewWillDisappear和 viewDidDisappear。然后在这个页面被 dismiss 的时候,将他弹出的那个 ViewController 的 viewWillAppear 和 viewDidAppear会被依次调用。然而使用默认的视差效果弹出页面,将他弹出的那个 ViewController 并不会调用这些方法,原先写在这四个函数中的代码以后都有可能会存在问题。

三、蓝牙权限更新

在 iOS 13 中,苹果将原来蓝牙申请权限用的 NSBluetoothPeripheralUsageDescription 字段,替换为 NSBluetoothAlwaysUsageDescription 字段。

iOS13适配_第1张图片
蓝牙权限.png

四、废弃UIWebview 改用 WKWebView

iOS13 开始苹果将 UIWebview 列为过期API(支持iOS2.0-iOS12)。 目前提交苹果应用市场(App Store)会发送邮件提示你在下一次提交时将应用中UIWebView 的 api 移除。

暂时没有强制使用WKWebView,但是在iOS13开始UIWebView已是废弃的API,以后更高的版本中防止出现问题,尽早移除是上上之策。

五、UISegmentedControl 默认样式改变。

默认样式变为 白底黑字,如果设置修改过颜色的话,页面需要修改。

六、WKWebView 中测量页面内容高度的方式变更

iOS 13以前 document.body.scrollHeight iOS 13中 document.documentElement.scrollHeight 两者相差55 应该是浏览器定义高度变了。

七、关于暗黑模式和切换

1、暗黑模式是iOS13的一大亮点,下面来看看模式切换的设置
(1) 切换 修改当前 UIViewController 或 UIView的模式。只要设置了控制器为暗黑模式,那么它子view也会对应的修改。

即:只会影响当前的视图,不会影响前面的 controller 和后续 present 的 controller。

if (@available(iOS 13.0, *)) {
   self.overrideUserInterfaceStyle =  UIUserInterfaceStyleDark;//UIUserInterfaceStyleLight
} else {
        // Fallback on earlier versions
}

注意啦⚠️,但是当我们在 window 上设置 overrideUserInterfaceStyle 的时候,就会影响 window 下所有的 controller, view,包括后续推出的 controller。

(2) 获取当前的模式
if (@available(iOS 12.0, *)) {
     if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ){
            // Dark
          NSLog(@"是dark模式、。。。");
     } else  if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) {
            // Light
          NSLog(@"是light模式、。。。");
     } else {
            //unspecified
          NSLog(@"是unspecified模式、。。。");
     }
}
(3) 监听模式的变化
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
    [super traitCollectionDidChange:previousTraitCollection];
// trait发生了改变
    if (@available(iOS 13.0, *)) {
        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
            // 执行操作
        }
    } else {
        // Fallback on earlier versions
    }
}
2、适配暗黑模式
(1)将同一个资源,创建出两种模式的样式。系统根据当前选择的样式,自动获取该样式的资源
(2)每次系统更新样式时,应用会调用当前所有存在的元素调用对应的一些重新方法,进行重绘视图,可以在对应的方法做相应的改动。
(3)资源文件适配
  • 1.创建一个Assets文件(或在现有的Assets文件中)
    详情请看:iOS中创建多个Assets.xcassets文件
  • 2.新建一个图片资源文件(或者颜色资源文件、或者其他资源文件)
  • 3.选中该资源文件, 打开 Xcode ->View ->Inspectors ->Show Attributes Inspectors (或者Option+Command+4)视图,将Apperances 选项 改为Any,Dark
  • 4.执行完第三步,资源文件将会有多个容器框,分别为 Any Apperance 和 Dark Apperance. Any Apperance 应用于默认情况(Unspecified)与高亮情况(Light), Dark Apperance 应用于暗黑模式(Dark)
  • 5.代码默认执行时,就可以正常通过名字使用了,系统会根据当前模式自动获取对应的资源文件

注意啦⚠️,同一工程内多个Assets文件在打包后,就会生成一个Assets.car 文件,所以要保证Assets内资源文件的名字不能相同.

  • 步骤如图:
    iOS13适配_第2张图片
    资源文件适配暗黑模式.png
iOS13适配_第3张图片
资源文件适配暗黑模式.png
3、全局关闭黑暗模式
  • 方式一 配置plist文件: 在Info.plist 文件中,添加UIUserInterfaceStyle key 名字为 User Interface Style 值为String,将UIUserInterfaceStyle key 的值设置为 Light。


    iOS13适配_第4张图片
    全局关闭黑暗模式.png

在开发中,如果用的系统控件(如cell、tableview的背景色)未设置背景色(或者为透明),则进入暗黑模式后,控件背景色变为黑色。

可以每一个页面设置,当然也可以整体设置, 一般我们的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
4、单个界面不遵循暗黑模式
  • UIViewController与UIView 都新增一个属性 overrideUserInterfaceStyle
  • 将 overrideUserInterfaceStyle 设置为对应的模式,则强制限制该元素与其子元素以设置的模式进行展示,不跟随系统模式改变进行改变

1、设置 ViewController 的该属性, 将会影响视图控制器的视图和子视图控制器采用该样式。
2、设置 View 的该属性, 将会影响视图及其所有子视图采用该样式。
3、设置 Window 的该属性, 将会影响窗口中的所有内容都采用样式,包括根视图控制器和在该窗口中显示内容的所有演示控制器(UIPresentationController)

更详细的暗黑模式设置请看:iOS适配暗黑模式

八、即将废弃的 LaunchImage(iOS 7.0–13.0)

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

注意啦⚠️,从2020年4月开始,所有使⽤ iOS13 SDK 的 App 将必须提供 LaunchScreen.storyboard,LaunchImage即将退出历史舞台,否则将无法提交到 App Store 进行审批。

九、StatusBar 与之前版本不同

目前状态栏也增加了一种模式,由之前的两种,变成了三种, 其中default由之前的黑色内容,变成了会根据系统模式,自动选择当前展示lightContent还是darkContent。

十、iOS13 获取window适配

    UIWindow* window = nil;
     
    if (@available(iOS 13.0, *)) {
        for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes)
        {
           if (windowScene.activationState == UISceneActivationStateForegroundActive)
           {
                window = windowScene.windows.firstObject;
     
                break;
           }
        }
    }else{
        window = [UIApplication sharedApplication].keyWindow;
    }

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