아이폰 OS 13 지원 적응 모델
최신 아이폰 (11), 아이폰 프로 (11)와 아이폰 (11) 프로 맥스
아이폰 X, 아이폰 XR, 아이폰 XS, 아이폰 XS 최대
아이폰 8, 아이폰 8 플러스
아이폰 7, 아이폰 7 플러스
아이폰 기가, 아이폰 기가 플러스
아이폰 SE
아이팟 터치 (第七代)
A, UI 레벨
참고 :이 지점에 적응해야 "(필수)"마크, 이것은 어떤 점 몇 가지 개인 항목을 참조 할 수 있어야 있어야합니다, 당신은 무시할 수
1.Dark 모드
디아블로 모드의 아이폰 OS 13 출시, UIKit 다른 API를 색상과 색상 모드에 맞게 새로운 시스템을 제공, 재료의 xcassets 적응이 조정 된 공식 특정 적응 볼 수 : 구현 다크 모드를 ON 아이폰 OS .
적응 방식 :
![](http://upload-images.jianshu.io/upload_images/10389949-772ce089a12dcf4b.png?imageMogr2/auto-orient/strip|imageView2/2/w/1114/format/webp)
참고 링크 :
https://mp.weixin.qq.com/s/qliFbqRdkkE30vslojfJCA
https://juejin.im/post/5cf6276be51d455a68490b26
애플과 2.Sign에서
애플에서가 사용할 수 있습니다 가입 이 여름에 베타 테스트. 그것은이 올해 상업적으로 사용할 수있는 경우에 로그인 타사 지원 응용 프로그램에서 사용자를위한 옵션으로 요구 될 것이다.
당신은 타사 응용 프로그램 지원을 사용하여 로그인하는 경우 애플에 소개 로그인 : 당신은 애플의 새로운 로그인을 추가해야합니다. 현재는 뉴스 및 업데이트 요구 사항 플러스 공식 자료에서 언급 한 애플은 출시 날짜는 아직 결정한다.
3. 기본 모드는 상호 변경을 팝업 (예정)
이 열거 치의에서 아이폰 OS (13)는 직접 presentViewController 시차 효과는 기본 쇠퇴를 반환하는 것입니다, 그대로 다음보기를 열 수 있도록, 모달 팝업 기본값이됩니다.
![](http://upload-images.jianshu.io/upload_images/10389949-17b874cf86afde08.gif?imageMogr2/auto-orient/strip|imageView2/2/w/360/format/webp)
iOS13에서 여전히 할 수있는 그것을 전체 화면 팝업, 필요성 스타일 UI의 용도를 결정하는
4.UISegmentedControl 기본 스타일 변경 (필수)
기본 스타일은 수정 된 색상을 설정하면, 다음 페이지를 수정해야, 검은 색과 흰색이된다.
![](http://upload-images.jianshu.io/upload_images/10389949-cbe15928781f0d68.png?imageMogr2/auto-orient/strip|imageView2/2/w/686/format/webp)
원래하려면 tintColor가 선택한 색상의 속성을 수정하기 위해 추가 selectedSegmentTintColor 실패한 색상을 선택하도록 설정.
웹 콘텐츠 어댑터
5.h5 적응, 참조 링크 :
https://blog.csdn.net/u012413955/article/details/92198556
둘째, 코드 레벨
1. KVC 개인 방법은 허용되지 않습니다 (필수)
forKey : 아이폰 OS 13에서 더 이상 valueForKey, setValue의 사용이 허용되지 않는 다른 방법을 컴파일 할 수 있지만, 취득 또는 개인 속성을 설정하지만, 런타임에 직접에 대한 충돌 정보와 팁을 충돌합니다 :
// 使用的私有方法
[_textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
// 崩溃提示信息
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug'
解决方案一:使用其他方法:(建议使用此种方法,因为第二种方法不知道能否过审)
// 替换的方案
_textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"输入"attributes:@{NSForegroundColorAttributeName: [UIColor redColor]}];
解决方案二:去掉keypath中的“_”
如果需要修改UISearchBar的placeholder,需要获取其searchTextField,可用category实现:
@implementation UISearchBar (SearchTextField) - (UITextField *)atu_searchTextField{ if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) { //判断xcode版本 #ifdef __IPHONE_13_0 return self.searchTextField; #else return [self valueForKey:@"searchTextField"]; #endif } //尝试过遍历subviews来找到,但是subviews中并不包含searchField!没有找到更好的办法 return [self valueForKey:@"searchField"]; } @end
如果有哪位大大有更好的办法,请告知
2.推送的 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); }
3.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; } }
4.使用 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";
5.模态弹出默认交互改变(必须)
如果需要做成全屏显示的界面,需要手动设置弹出样式:
- (UIModalPresentationStyle)modalPresentationStyle { return UIModalPresentationFullScreen; } //或者在present之前: ctr.modalPresentationStyle = UIModalPresentationFullScreen; [self presentViewController:ctr animated:animated completion:completion];
如果项目里面有大量的位置使用了presentViewController,然后你不想每个位置都显式的手动修改,推荐这篇文章https://juejin.im/post/5d5f96866fb9a06b0517f78c
里面的方法,原理是:
通过runtime修改ctr.modalPresentationStyle的默认值为UIModalPresentationFullScreen(原默认值为UIModalPresentationPageSheet)如果需要禁止自动修改默认值
ctr.LL_automaticallySetModalPresentationStyle = NO;
当然,如果有精力,不太多的话还是建议老老实实的一个个手动修改,以免以后官方api再次变动。
有一点注意的是,ctr的生命周期方法调用情况会改变,假设有a,b两个ctr,在a中present出b:
全屏present时(UIModalPresentationFullScreen)的方法调用顺序:
a---viewWillDisappear:
b---viewWillAppear:
b---viewDidAppear:
a---viewDidDisappear:
dissmiss时的方法调用顺序:
b---viewWillDisappear:
a---viewWillAppear:
a---viewDidAppear:
b---viewDidDisappear:
非全屏presnet时(UIModalPresentationPageSheet)的方法调用顺序:
b---viewWillAppear:
b---viewDidAppear:
dissmiss时的方法调用顺序:
b---viewWillDisappear:
b---viewDidDisappear:
可以看出,以UIModalPresentationPageSheet的方式来present/dismiss时,分别少调用了a的两个方法,如果之前在这个位置有相关的逻辑代码,比如网络请求,UI刷新,要注意
6.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。
7.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 进行审批。
8.Xcode 11 创建的工程在低版本设备上运行黑屏
使用 Xcode 11 创建的工程,运行设备选择 iOS 13.0 以下的设备,运行应用时会出现黑屏。这是因为 Xcode 11 默认是会创建通过 UIScene 管理多个 UIWindow 的应用,工程中除了 AppDelegate 外会多一个 SceneDelegate.
![](http://upload-images.jianshu.io/upload_images/10389949-3bd5a28b99637789.png?imageMogr2/auto-orient/strip|imageView2/2/w/922/format/webp)
这是为了 iPadOS 的多进程准备的,也就是说 UIWindow 不再是 UIApplication 中管理。但是旧版本根本没有 UIScene,因此解决方案就是在 AppDelegate 的头文件加上:
@property (strong, nonatomic) UIWindow *window;
9.使用 @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
10.textfield.leftview(必须)
如下方式,直接给 textfield.leftView 赋值一个 UILabel 对象,他的宽高会被 sizeToFit,而不是创建时的值。
// left view label UILabel *phoneLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 63, 50)]; phoneLabel.text = @"手机号"; phoneLabel.font = [UIFont systemFontOfSize:16]; // set textfield left view self.textfieldName.leftView = phoneLabel;
如所看到,实际leftview的width为59,height为19:
![](http://upload-images.jianshu.io/upload_images/10389949-46755dfdbbed9e42.png?imageMogr2/auto-orient/strip|imageView2/2/w/300/format/webp)
解决方法:嵌套一个UIView
// label UILabel *phoneLabel = [[UILabel alloc] init]; phoneLabel.text = @"手机号"; phoneLabel.font = [UIFont systemFontOfSize:16]; [phoneLabel sizeToFit]; phoneLabel.centerY = 50/2.f; // left view UIView *leftView = [[UIView alloc] initWithFrame:(CGRect){0, 0, 63, 50}]; [leftView addSubview:phoneLabel]; // set textfield left view self.textfieldName.leftView = leftView;
11.NSAttributedString优化
对于UILabel、UITextField、UITextView,在设置NSAttributedString时也要考虑适配Dark Mode,否则在切换模式时会与背景色融合,造成不好的体验
不建议的做法
NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16]}; NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
推荐的做法
// 添加一个NSForegroundColorAttributeName属性 NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:[UIColor labelColor]}; NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
12.TabBar红点偏移
如果之前有通过TabBar上图片位置来设置红点位置,在iOS13上会发现显示位置都在最左边去了。遍历UITabBarButton的subViews发现只有在TabBar选中状态下才能取到UITabBarSwappableImageView,解决办法是修改为通过UITabBarButton的位置来设置红点的frame
13.废弃UIWebView(必须)
UIWebView在12.0就已经被废弃,部分APP使用webview时, 审核被拒
14.WKWebView 中测量页面内容高度的方式变更
iOS 13以前 document.body.scrollHeight iOS 13中 document.documentElement.scrollHeight 两者相差55 应该是浏览器定义高度变了
15.使用MJExtension 中处理NSNull的不同(必须)
这个直接会导致Crash的在将服务端数据字典转换为模型时,如果遇到服务端给的数据为NSNull时, mj_JSONObject,其中 class_copyPropertyList方法得到的属性里,多了一种EFSQLBinding类型的东西,而且属性数量也不准确, 那就没办法了, 我只能改写这个方法了,这个组件没有更新的情况下,写了一个方法swizzling掉把当遇到 NSNull时,直接转为nil了。
有人问这个方法怎么实现,其实目前我们项目在ios13下面还没遇到这种情况,发一个之前项目处理null的方法,在项目里面写一个NSObject的分类,添加下面的方法就可以了。大家请根据项目情况、数据格式修改下这个方法,MJ库里面自己会进行替换的:
- (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property { //为了解决json字符串先赋值给oc字典后,类型转换crash问题,如: //json->oldValue:0 //model中值为NSString类型 //如果先将json转为dic,dic中对应value值为NSNumber类型,则向oldValue发送isEqualToString消息会crash id tempValue = oldValue; if ([property.type.code isEqualToString:@"NSString"]) { tempValue = [NSString stringWithFormat:@"%@", tempValue]; if ([tempValue isKindOfClass:[NSNull class]] || tempValue == nil || [tempValue isEqual:[NSNull null]] || [tempValue isEqualToString:@"(null)"] || [tempValue isEqualToString:@"(\n)"] ) { return @""; } } if ([property.type.code isEqualToString:@"NSNumber"]) { // tempValue = [NSNumber numberWithFloat:[tempValue floatValue]]; if ([tempValue isKindOfClass:[NSNull class]] || tempValue == nil || [tempValue isEqual:[NSNull null]] || [tempValue isEqualToString:@"(null)"] || [tempValue isEqualToString:@"(\n)"] ) { return @0; } } return tempValue; }
16.StatusBar 与之前版本不同(必须)
之前 Status Bar 有两种状态,default 和 lightContent
现在 Status Bar 有三种状态,default, darkContent 和 lightContent
现在的 darkContent 对应之前的 default,现在的 default 会根据情况自动选择 darkContent 和 lightContent
17.UIActivityIndicatorView(必须)
之前的 UIActivityIndicatorView 有三种 style 分别为 whiteLarge, white 和 gray,现在全部废弃。
增加两种 style 分别为 medium 和 large,指示器颜色用 color 属性修改。
18.蓝牙权限需要申请
CBCentralManager,iOS13以前,使用蓝牙时可以直接用,不会出现权限提示,iOS13后,再使用就会提示了。 在info.plist里增加
<key>NSBluetoothAlwaysUsageDescription</key> <string>我们要一直使用您的蓝牙</string>
在iOS13中,蓝牙变成了和位置,通知服务等同样的可以针对单个app授权的服务。
- (NSString*) getWifiSsid { if (@available(iOS 13.0, *)) { //用户明确拒绝,可以弹窗提示用户到设置中手动打开权限 if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { NSLog(@"User has explicitly denied authorization for this application, or location services are disabled in Settings."); //使用下面接口可以打开当前应用的设置页面 //[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; return nil; } CLLocationManager* cllocation = [[CLLocationManager alloc] init]; if(![CLLocationManager locationServicesEnabled] || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined){ //弹框提示用户是否开启位置权限 [cllocation requestWhenInUseAuthorization]; usleep(50); //递归等待用户选选择 return [self getWifiSsidWithCallback:callback]; } } NSString *wifiName = nil; CFArrayRef wifiInterfaces = CNCopySupportedInterfaces(); if (!wifiInterfaces) { return nil; } NSArray *interfaces = (__bridge NSArray *)wifiInterfaces; for (NSString *interfaceName in interfaces) { CFDictionaryRef dictRef = CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interfaceName)); if (dictRef) { NSDictionary *networkInfo = (__bridge NSDictionary *)dictRef; NSLog(@"network info -> %@", networkInfo); wifiName = [networkInfo objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID]; CFRelease(dictRef); } } CFRelease(wifiInterfaces); return wifiName; }
19.CNCopyCurrentNetworkInfo
iOS13 以后只有开启了 Access WiFi Information capability,才能获取到 SSID 和 BSSID wi-fi or wlan 相关使用变更
最近收到了苹果的邮件,说获取WiFi SSID的接口CNCopyCurrentNetworkInfo 不再返回SSID的值。不仔细看还真会被吓一跳,对物联网的相关APP简直是炸弹。仔细看邮件还好说明了可以先获取用户位置权限才能返回SSID。
注意:目本身已经打开位置权限,则可以直接获取
参考链接,如有侵权,请告知,
https://github.com/ChenYilong/iOS13AdaptationTips/issues
iOS 13 适配
iOS13适配
适配 iOS13
Xcode11 和 iOS13 适配
iOS13 UI & 功能适配
解决Xcode11-beta版本新创建iOS工程低版本黑屏的问题
Modernizing Your UI for iOS13