iOS13适配更新总结

前言:

iOS13的API的变动和适配问题,我从新特性适配、API 适配、方法弃用、工程适配、SDK 适配、其他问题分类归纳整理。

新特性适配
1.暗黑模式Dark Mode
2.苹果登录 Sign In with Apple

API 适配
1.模态视图弹出方式改变
2.私有方法 KVC 被禁用
3.searchBar设置textField问题
4.UISearchBar 黑线处理导致崩溃
5.推送的 deviceToken 获取到的格式发生变化
6.UISegmentedControl默认样式改变

方法弃用
1.UIWebView 将被禁止提交审核
2.使用 UISearchDisplayController 导致崩溃
3.MPMoviePlayerController 被弃用

工程适配
1.LaunchImage 被弃用
2.iOS13 新生命周期 SceneDelegate
3.蓝牙权限字段更新导致崩溃以及提交审核失败
4.CNCopyCurrentNetworkInfo 使用要求更严格
5.修改APP名称(修改DisplayName值)

SDK 适配
1.使用 @available 导致旧版本 Xcode 编译出错。

其他问题
1.[_LSDefaults sharedInstance]崩溃

一、新特性适配

1.暗黑模式Dark Mode

iOS 13 推出暗黑模式,UIView默认背景色会变成暗黑色。适配暗黑模式的工作量较大,改为强制使用正常模式。

处理方案:在plist文件中增加配置项UIUserInterfaceStyle,值为Light。

2.苹果登录 Sign In with Apple

关于苹果登录,如果应用没有使用第三方登录,可以不用添加,如果 APP 支持三方登陆(Facbook、Google、微信、QQ、支付宝、微博等),就必须支持苹果登陆,且要放前边。

建议支持使用Apple提供的按钮样式,已经适配各类设备。

2019 年 9 月 12 日 起,提交到 App Store 的新应用必须按照应用审核指南中的标准进行接入;现有应用和应用更新必须也在 2020 年 4 月前完成接入。

二、API 适配

1. 模态视图弹出方式改变

在 iOS 13 UIModalPresentationStyle 枚举的定义中,苹果新加了一个枚举值:

typedef NS_ENUM(NSInteger, UIModalPresentationStyle) {
    ...
    UIModalPresentationAutomatic API_AVAILABLE(ios(13.0)) = -2,
}

新的交互方式默认为下拉dismiss,且导航栏会留白空出;
如果需要点击取消按钮才消失界面的,需要适配;
如果你完全接受苹果的这个默认效果,那就不需要去修改任何代码。
如果需要做成全屏显示的界面,需要手动设置弹出样式:

- (UIModalPresentationStyle)modalPresentationStyle {
    return UIModalPresentationFullScreen;
}

如果,之前代码已经设置了modalPresentationStyle的值,那你也不会有这个影响。对于想要找回原来默认交互的同学,直接设置如下即可:

在presentViewController之前加入这个代码:

[self presentViewController:nav animated:YES completion:nil];

nav.modalPresentationStyle = UIModalPresentationFullScreen;

2.私有方法 KVC 被禁用

iOS13后不再允许valueForKey、setValue:forKey: 等方法获取或设置私有属性,虽然编译可以通过,但是在运行时会直接崩溃或不起作用。

👇下面的方法都没有用了哦
// UITextField 的 _placeholderLabel
[textField setValue:[UIColor blackColor] forKeyPath:@"_placeholderLabel.textColor"];
 
// UISearchBar 的 _searchField
[searchBar valueForKey:@"_searchField"];

解决方案一:富文本设置

UITextField有一个attributedPlaceholder属性,占位符富文本,和UILabel等控件的富文本设置一样,可以设置文字颜色,尺寸等。

UITextField *textField = [[UITextField alloc]init];
textField.placeholder = @"我是占位符";
[self.view addSubview:textField];
        
NSMutableAttributedString *attribute_placeholder = [[NSMutableAttributedString alloc]initWithString:textField.placeholder];
[attribute_placeholder addAttribute:NSForegroundColorAttributeName
                              value:[UIColor colorWithWhite:1 alpha:0.2]
                              range:NSMakeRange(0, textField.placeholder.length)];
[attribute_placeholder addAttribute:NSFontAttributeName
                              value:[UIFont systemFontOfSize:14]
                              range:NSMakeRange(0, textField.placeholder.length)];
[searchField setAttributedPlaceholder:attribute_placeholder];

解决方案二:用runtime处理解决TextField的KVC崩溃问题

添加以下代码,老代码不用作任何修改

// 用runtime处理解决TextField的KVC崩溃问题
+ (void)load {
    Method origin = class_getInstanceMethod([self class], @selector(valueForKey:));
    Method swizzing = class_getInstanceMethod([self class], @selector(swizzing_valueForKey:));
    if (class_addMethod([self class], @selector(valueForKey:), method_getImplementation(swizzing), method_getTypeEncoding(swizzing))) {
        class_replaceMethod([self class], @selector(swizzing_valueForKey:), method_getImplementation(origin), method_getTypeEncoding(origin));
    }
    method_exchangeImplementations(origin, swizzing);
}

- (id)swizzing_valueForKey:(NSString *)key {
    if ([key isEqualToString:@"_placeholderLabel"]) {
        Ivar ivar = class_getInstanceVariable([self class], [key UTF8String]);
        id value = object_getIvar(self, ivar);

        return value;
    } else {
        return [self swizzing_valueForKey:key];
    }
}

3.searchBar设置textField问题

我们不用再用kvc获取UISearchBar的textField了,因为,iOS13中系统将searchTextField属性暴露出来了,不再是私有属性了,你可以直接调用。

UITextField *searchField = _searchBar.searchTextField;

注意:以上写法在iOS13以下手机运行会崩溃,所以,暂时要写成两种情况的写法,iOS13依然沿用之前的旧写法。
[[[UIDevice currentDevice] systemVersion] doubleValue] >= 13.0加以区分

4.UISearchBar 黑线处理导致崩溃

之前为了处理搜索框的黑线问题,通常会遍历 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 removeFromSuperview];
        view.layer.contents = nil;
        break;
    }

5. 推送的 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 }

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

#include <arpa/inet.h>
- (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);
}

6. UISegmentedControl默认样式改变

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

原本设置选中颜色的 tintColor 已经失效,新增了 selectedSegmentTintColor 属性用以修改选中的颜色。

    if (@available(iOS 13.0, *)) {
        sc.selectedSegmentTintColor = ZLColor(113, 112, 118);
    } else {
         sc.tintColor = ZLColor(113, 112, 118);
    }

iOS12以前设置圆角:

testSegmentedControl.layer.masksToBounds = true
testSegmentedControl.layer.cornerRadius = 10

但是iOS13系统下,完全不起作用,后来找到了方法,可以用重写UISegmentedControl的layoutSubviews方法来设置圆角。

class TestSegmentedControl: UISegmentedControl {

    open override func layoutSubviews() {
        super.layoutSubviews()
        layer.cornerRadius = 10
    }

}

这样子我们用TestSegmentedControl作为子类重写父类的layoutSubviews的方法,就又可以设置圆角啦。

如果项目有需求一定要在iOS13系统上实现iOS12以前的风格样式,可以自定义一个category来实现:

@interface UISegmentedControl (Style_OC)

/// UISegmentedControl 将iOS13风格转化成iOS12之前的风格样式
- (void)ensureiOS12Style;
@end
@implementation UISegmentedControl (Style_OC)
- (void)ensureiOS12Style {
    // UISegmentedControl has changed in iOS 13 and setting the tint
    // color now has no effect.
    if (@available(iOS 13, *)) {
        UIColor *tintColor = [self tintColor];
        UIImage *tintColorImage = [self imageWithColor:tintColor];
        // Must set the background image for normal to something (even clear) else the rest won't work
        [self setBackgroundImage:[self imageWithColor:self.backgroundColor ? self.backgroundColor : [UIColor clearColor]] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [self setBackgroundImage:tintColorImage forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
        [self setBackgroundImage:[self imageWithColor:[tintColor colorWithAlphaComponent:0.2]] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
        [self setBackgroundImage:tintColorImage forState:UIControlStateSelected|UIControlStateSelected barMetrics:UIBarMetricsDefault];
        [self setTitleTextAttributes:@{NSForegroundColorAttributeName: tintColor, NSFontAttributeName: [UIFont systemFontOfSize:13]} forState:UIControlStateNormal];
        [self setDividerImage:tintColorImage forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        self.layer.masksToBounds = YES;
        self.layer.borderWidth = 1;
        self.layer.borderColor = [tintColor CGColor];
       
        
    }
}

- (UIImage *)imageWithColor: (UIColor *)color {
    CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return theImage;
}
@end

三、方法弃用

1.UIWebView 将被禁止提交审核

在 iOS 13 推出后,苹果在 UIWebView 的说明上将其支持的系统范围定格在了 iOS 2 ~ iOS 12。目前,如果开发者将包含 UIWebView api 的应用更新上传到 App Store 审核后,其将会收到包含 ITMS-90809 信息的回复邮件,提示你在下一次提交时将应用中 UIWebView 的 api 移除。

解决方案:
WKWebView 替代 UIWebView,确保所有 UIWebView 的 api 都要移除,如果需要适配 iOS 7 的可以通过 openURL 的方式在 Safari 打开。

2. 使用 UISearchDisplayController 导致崩溃

在 iOS 8 之前,我们在 UITableView 上添加搜索框需要使用 UISearchBar + UISearchDisplayController 的组合方式,而在 iOS 8 之后,苹果就已经推出了 UISearchController 来代替这个组合方式。在 iOS 13 中,如果还继续使用 UISearchDisplayController 会直接导致崩溃,崩溃信息如下:

** Terminating app due to uncaught exception 'NSGenericException',
 reason: 'UISearchDisplayController is no longer supported when linking against this version of iOS. Please migrate your application to UISearchController.' 

另外说一下,在 iOS 13 中终于可以获取直接获取搜索的文本框:

_searchBar.searchTextField.text = @"search";

3.MPMoviePlayerController 被弃用

在 iOS 9 之前播放视频可以使用 MediaPlayer.framework 中的MPMoviePlayerController类来完成,它支持本地视频和网络视频播放。但是在 iOS 9 开始被弃用,如果在 iOS 13 中继续使用的话会直接抛出异常:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: 'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'

解决方案:使用 AVFoundation 里的 AVPlayer。

四、工程适配

1.LaunchImage 被弃用

iOS 8 之前我们是在LaunchImage 来设置启动图,但是随着苹果设备尺寸越来越多,我们需要在对应的 aseets 里面放入所有尺寸的启动图,这是非常繁琐的一个步骤。因此在 iOS 8 苹果引入了 LaunchScreen.storyboard,支持界面布局用的 AutoLayout + SizeClass ,可以很方便适配各种屏幕。
需要注意的是,苹果在 Modernizing Your UI for iOS 13 section 中提到
,从2020年4月开始,所有支持 iOS 13 的 App 必须提供 LaunchScreen.storyboard,否则将无法提交到 App Store 进行审批。

解决方案:
使用 LaunchScreen.storyboard 设置启动页,弃用 LaunchImage。

2. iOS13 新生命周期 SceneDelegate

Xcode 11 创建的工程在低版本设备上运行黑屏
使用 Xcode 11 创建的工程,运行设备选择 iOS 13.0 以下的设备,运行应用时会出现黑屏。这是因为 Xcode 11 默认是会创建通过 UIScene 管理多个 UIWindow 的应用,工程中除了 AppDelegate 外会多一个 SceneDelegate:

增加了 #import "SceneDelegate.h"
原来的 #import "AppDelegate.h" 没有了window

要想维持原状,需要在AppDelegate的头文件里,添加window,并且删掉Info.plist里的新增键值对

@interface AppDelegate : UIResponder 
@property (strong, nonatomic) UIWindow *window;
@end

删掉以下键值对
Application Scene Manifest:2times
Main storyboard file base name:Main


3. 蓝牙权限字段更新导致崩溃以及提交审核失败

在 iOS 13 中,苹果将原来蓝牙申请权限用的 NSBluetoothPeripheralUsageDescription 字段,替换为 NSBluetoothAlwaysUsageDescription 字段。
如果在 iOS 13 中使用旧的权限字段获取蓝牙权限,会导致崩溃,崩溃信息如下:

This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSBluetoothAlwaysUsageDescription key with a string value explaining to the user how the app uses this data.

另外,如果将没有新字段的包提交审核,将会收到包含 ITMS-90683 的邮件,并提示审核不通过。

解决方案

官网文档也有说明,就是在 Info.plist 中把两个字段都加上。

For deployment targets earlier than iOS 13, add both `NSBluetoothAlwaysUsageDescription` and `NSBluetoothPeripheralUsageDescription` to your app’s Information Property List file.

4. CNCopyCurrentNetworkInfo 使用要求更严格

从 iOS 12 开始,CNCopyCurrentNetworkInfo 函数需要开启 Access WiFi Information 的功能后才会返回正确的值。在 iOS 13 中,这个函数的使用要求变得更严格,根据 CNCopyCurrentNetworkInfo 文档说明,应用还需要符合下列三项条件中的至少一项才能得到正确的值:

  • 使用 Core Location 的应用, 并获得定位服务权限。
  • 使用 NEHotspotConfiguration 来配置 WiFi 网络的应用。
  • 目前正处于启用状态的 VPN 应用。

苹果作出这项改变主要为了保障用户的安全,因为根据 MAC 地址容易推算出用户当前所处的地理位置。同样,蓝牙设备也具有 MAC 地址,所以苹果也为蓝牙添加了新的权限,可见蓝牙上这一点。

解决方案:
根据应用需求,添加三项要求其中一项。可以选择第一项获取定位权限,因为添加的成本不会太大,只需要用户允许应用使用定位服务即可。

5. 修改APP名称(修改DisplayName值)

  • 在Xcode创建项目时默认的project.pbxproj中的所有PRODUCT_NAME = "$(TARGET_NAME)"

  • 在Xcode11.0之前如果修改DisplayName时只是修改info.plist中的Bundle display name值,但是在Xcode11.0中修改该值则会把project.pbxproj中的一个PRODUCT_NAME改为修改后值,如果在项目中通过[NSBundle mainBundle] infoDictionary]取kCFBundleExecutableKey的就会有影响,并且对Build Settings中的Packaing中的一些名称有影响。

五、SDK 适配

1. 使用 @available 导致旧版本 Xcode 编译出错。

在 Xcode 11 的 SDK 工程的代码里面使用了 @available 判断当前系统版本,打出来的包放在 Xcode 10 中编译,会出现一下错误:

Undefine symbols for architecture i386:
    "__isPlatformVersionAtLeast", referenced from:
        ...
ld: symbol(s) not found for architecture i386复制代码

从错误信息来看,是 __isPlatformVersionAtLeast 方法没有具体的实现,但是工程里根本没有这个方法。实际测试无论在哪里使用@available ,并使用 Xcode 11 打包成动态库或静态库,把打包的库添加到 Xcode 10 中编译都会出现这个错误,因此可以判断是 iOS 13 的 @available 的实现中使用了新的 api。

解决方案:
如果你的 SDK 需要适配旧版本的 Xcode,那么需要避开此方法,通过获取系统版本来进行判断:

if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
    ...
}

另外,在 Xcode 10 上打开 SDK 工程也应该可以正常编译,这就需要加上编译宏进行处理:

#ifndef __IPHONE_13_0
#define __IPHONE_13_0 130000
#endif

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
...
#endif

六、其他问题

1. [_LSDefaults sharedInstance]崩溃

遇到以下崩溃问题

+[_LSDefaults sharedInstance]: unrecognized selector sent to class 0x1dd8f5d40

解决方案:
可以尝试pod updatepod库,暂时可以解决问题。

iOS13的API的变动和适配问题,在工作中可能会有不同的问题和方式出现,会持续更新呦~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容