iOS 13 支持適配的機(jī)型
iPhone X簿透、iPhone XR斑响、iPhone XS恩脂、iPhone XS Max
iPhone 8畸颅、iPhone 8 Plus
iPhone 7、iPhone 7 Plus
iPhone 6s、iPhone 6s Plus
iPhone SE
iPod touch (第七代)
一换棚、UI層面
注:必須適配的點(diǎn)以“(必須)”標(biāo)出式镐,這個(gè)必須有的點(diǎn)是一定要做的,有的是看個(gè)人項(xiàng)目固蚤,大家可忽略
1.Dark Mode
iOS 13 推出暗黑模式娘汞,UIKit 提供新的系統(tǒng)顏色和 api 來(lái)適配不同顏色模式,xcassets 對(duì)素材適配也做了調(diào)整夕玩,官方具體適配可見: Implementing Dark Mode on iOS你弦。
適配方案:
參考鏈接:
https://mp.weixin.qq.com/s/qliFbqRdkkE30vslojfJCA
https://juejin.im/post/5cf6276be51d455a68490b26
2.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.
如果你的應(yīng)用支持使用第三方登錄,那么就必須加上蘋果新推出的登錄方式:Introducing Sign In with Apple燎孟。目前蘋果只在 News and Updates 上提到正式發(fā)布時(shí)要求加上禽作,具體發(fā)布時(shí)間還沒確定。
3.模態(tài)彈出默認(rèn)交互改變(必須)
在 iOS 13 中此枚舉值直接成為了模態(tài)彈出的默認(rèn)值揩页,因此 presentViewController 方式打開視圖是如下的視差效果旷偿,默認(rèn)是下滑返回。
iOS13下仍然可以做到全屏彈出爆侣,這里需要UI決定采用哪種樣式
4.UISegmentedControl 默認(rèn)樣式改變(必須)
默認(rèn)樣式變?yōu)榘椎缀谧制汲蹋绻O(shè)置修改過(guò)顏色的話,頁(yè)面需要修改兔仰。
原本設(shè)置選中顏色的 tintColor 已經(jīng)失效茫负,新增了 selectedSegmentTintColor 屬性用以修改選中的顏色。
Web Content適配
5.h5的適配斋陪,參考鏈接:
https://blog.csdn.net/u012413955/article/details/92198556
二朽褪、代碼層面
1.私有方法 KVC 不允許使用(必須)
在 iOS 13 中不再允許使用 valueForKey、setValue:forKey: 等方法獲取或設(shè)置私有屬性无虚,雖然編譯可以通過(guò)缔赠,但是在運(yùn)行時(shí)會(huì)直接崩潰,并提示一下崩潰信息:
// 使用的私有方法
[_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'
解決方案一:使用其他方法:(建議使用此種方法,因?yàn)榈诙N方法不知道能否過(guò)審)
// 替換的方案
_textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"輸入"attributes:@{NSForegroundColorAttributeName: [UIColor redColor]}];
解決方案二:去掉keypath中的“_”
如果需要修改UISearchBar的placeholder,需要獲取其searchTextField囤耳,可用category實(shí)現(xiàn):
@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
}
//嘗試過(guò)遍歷subviews來(lái)找到绢掰,但是subviews中并不包含searchField菜循!沒有找到更好的辦法
return [self valueForKey:@"searchField"];
}
@end
如果有哪位大大有更好的辦法,請(qǐng)告知
2.推送的 deviceToken 獲取到的格式發(fā)生變化(必須)
原本可以直接將 NSData 類型的 deviceToken 轉(zhuǎn)換成 NSString 字符串,然后替換掉多余的符號(hào)即可:
- (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 中,這種方法已經(jīng)失效离唬,NSData類型的 deviceToken 轉(zhuǎn)換成的字符串變成了:
{length = 32, bytes = 0xd7f9fe34 69be14d1 fa51be22 329ac80d ... 5ad13017 b8ad0736 }
需要進(jìn)行一次數(shù)據(jù)格式處理,參考友盟的做法划鸽,可以適配新舊系統(tǒng)输莺,獲取方式如下:
#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 黑線處理導(dǎo)致崩潰
之前為了處理搜索框的黑線問(wèn)題戚哎,通常會(huì)遍歷 searchBar 的 subViews,找到并刪除 UISearchBarBackground嫂用,在 iOS13 中這么做會(huì)導(dǎo)致 UI 渲染失敗型凳,然后直接崩潰,崩潰信息如下:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Missing or detached view for search bar layout'
解決辦法是設(shè)置 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 導(dǎo)致崩潰
在 iOS 8 之前嘱函,我們?cè)?UITableView 上添加搜索框需要使用 UISearchBar + UISearchDisplayController 的組合方式甘畅,而在 iOS 8 之后,蘋果就已經(jīng)推出了 UISearchController 來(lái)代替這個(gè)組合方式往弓。在 iOS 13 中疏唾,如果還繼續(xù)使用 UISearchDisplayController 會(huì)直接導(dǎo)致崩潰,崩潰信息如下:
*** 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.'
另外說(shuō)一下亮航,在 iOS 13 中終于可以獲取直接獲取搜索的文本框:
_searchBar.searchTextField.text = @“search";
5.模態(tài)彈出默認(rèn)交互改變(必須)
如果需要做成全屏顯示的界面荸实,需要手動(dòng)設(shè)置彈出樣式:
- (UIModalPresentationStyle)modalPresentationStyle {
return UIModalPresentationFullScreen;
}
//或者在present之前:
ctr.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:ctr animated:animated completion:completion];
如果項(xiàng)目里面有大量的位置使用了presentViewController匀们,然后你不想每個(gè)位置都顯式的手動(dòng)修改缴淋,推薦這篇文章https://juejin.im/post/5d5f96866fb9a06b0517f78c
里面的方法,原理是:
通過(guò)runtime修改ctr.modalPresentationStyle的默認(rèn)值為UIModalPresentationFullScreen(原默認(rèn)值為UIModalPresentationPageSheet)如果需要禁止自動(dòng)修改默認(rèn)值
ctr.LL_automaticallySetModalPresentationStyle = NO;
當(dāng)然泄朴,如果有精力重抖,不太多的話還是建議老老實(shí)實(shí)的一個(gè)個(gè)手動(dòng)修改,以免以后官方api再次變動(dòng)祖灰。
有一點(diǎn)注意的是钟沛,ctr的生命周期方法調(diào)用情況會(huì)改變,假設(shè)有a,b兩個(gè)ctr局扶,在a中present出b:
全屏present時(shí)(UIModalPresentationFullScreen)的方法調(diào)用順序:
a---viewWillDisappear:
b---viewWillAppear:
b---viewDidAppear:
a---viewDidDisappear:
dissmiss時(shí)的方法調(diào)用順序:
b---viewWillDisappear:
a---viewWillAppear:
a---viewDidAppear:
b---viewDidDisappear:
非全屏presnet時(shí)(UIModalPresentationPageSheet)的方法調(diào)用順序:
b---viewWillAppear:
b---viewDidAppear:
dissmiss時(shí)的方法調(diào)用順序:
b---viewWillDisappear:
b---viewDidDisappear:
可以看出恨统,以UIModalPresentationPageSheet的方式來(lái)present/dismiss時(shí),分別少調(diào)用了a的兩個(gè)方法三妈,如果之前在這個(gè)位置有相關(guān)的邏輯代碼畜埋,比如網(wǎng)絡(luò)請(qǐng)求,UI刷新畴蒲,要注意
6.MPMoviePlayerController 被棄用
在 iOS 9 之前播放視頻可以使用 MediaPlayer.framework 中的MPMoviePlayerController類來(lái)完成悠鞍,它支持本地視頻和網(wǎng)絡(luò)視頻播放。但是在 iOS 9 開始被棄用模燥,如果在 iOS 13 中繼續(xù)使用的話會(huì)直接拋出異常:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
解決方案是使用 AVFoundation 里的 AVPlayer咖祭。
7.LaunchImage 被棄用(必須)
iOS 8 之前我們是在LaunchImage 來(lái)設(shè)置啟動(dòng)圖,但是隨著蘋果設(shè)備尺寸越來(lái)越多蔫骂,我們需要在對(duì)應(yīng)的 aseets 里面放入所有尺寸的啟動(dòng)圖么翰,這是非常繁瑣的一個(gè)步驟。因此在 iOS 8 蘋果引入了 LaunchScreen.storyboard辽旋,支持界面布局用的 AutoLayout + SizeClass 浩嫌,可以很方便適配各種屏幕。
需要注意的是,蘋果在 Modernizing Your UI for iOS 13 section 中提到固该,從2020年4月開始锅减,所有支持 iOS 13 的 App 必須提供 LaunchScreen.storyboard,否則將無(wú)法提交到 App Store 進(jìn)行審批伐坏。
8.Xcode 11 創(chuàng)建的工程在低版本設(shè)備上運(yùn)行黑屏
使用 Xcode 11 創(chuàng)建的工程怔匣,運(yùn)行設(shè)備選擇 iOS 13.0 以下的設(shè)備,運(yùn)行應(yīng)用時(shí)會(huì)出現(xiàn)黑屏桦沉。這是因?yàn)?Xcode 11 默認(rèn)是會(huì)創(chuàng)建通過(guò) UIScene 管理多個(gè) UIWindow 的應(yīng)用每瞒,工程中除了 AppDelegate 外會(huì)多一個(gè) SceneDelegate.
這是為了 iPadOS 的多進(jìn)程準(zhǔn)備的,也就是說(shuō) UIWindow 不再是 UIApplication 中管理纯露。但是舊版本根本沒有 UIScene剿骨,因此解決方案就是在 AppDelegate 的頭文件加上:
@property (strong, nonatomic) UIWindow *window;
9.使用 @available 導(dǎo)致舊版本 Xcode 編譯出錯(cuò)。(必須)
在 Xcode 11 的 SDK 工程的代碼里面使用了 @available 判斷當(dāng)前系統(tǒng)版本埠褪,打出來(lái)的包放在 Xcode 10 中編譯浓利,會(huì)出現(xiàn)一下錯(cuò)誤:
Undefine symbols for architecture i386:
"__isPlatformVersionAtLeast", referenced from:
...
ld: symbol(s) not found for architecture i386
從錯(cuò)誤信息來(lái)看,是 __isPlatformVersionAtLeast 方法沒有具體的實(shí)現(xiàn)钞速,但是工程里根本沒有這個(gè)方法贷掖。實(shí)際測(cè)試無(wú)論在哪里使用@available ,并使用 Xcode 11 打包成動(dòng)態(tài)庫(kù)或靜態(tài)庫(kù)渴语,把打包的庫(kù)添加到 Xcode 10 中編譯都會(huì)出現(xiàn)這個(gè)錯(cuò)誤苹威,因此可以判斷是 iOS 13 的 @available 的實(shí)現(xiàn)中使用了新的 api。如果你的 SDK 需要適配舊版本的 Xcode驾凶,那么需要避開此方法牙甫,通過(guò)獲取系統(tǒng)版本來(lái)進(jìn)行判斷:
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
...
}
另外,在 Xcode 10 上打開 SDK 工程也應(yīng)該可以正常編譯调违,這就需要加上編譯宏進(jìn)行處理:
#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 賦值一個(gè) UILabel 對(duì)象,他的寬高會(huì)被 sizeToFit翰萨,而不是創(chuàng)建時(shí)的值脏答。
// left view label
UILabel *phoneLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 63, 50)];
phoneLabel.text = @"手機(jī)號(hào)";
phoneLabel.font = [UIFont systemFontOfSize:16];
// set textfield left view
self.textfieldName.leftView = phoneLabel;
如所看到,實(shí)際leftview的width為59亩鬼,height為19:
解決方法:嵌套一個(gè)UIView
// label
UILabel *phoneLabel = [[UILabel alloc] init];
phoneLabel.text = @"手機(jī)號(hào)";
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優(yōu)化
對(duì)于UILabel殖告、UITextField、UITextView雳锋,在設(shè)置NSAttributedString時(shí)也要考慮適配Dark Mode黄绩,否則在切換模式時(shí)會(huì)與背景色融合,造成不好的體驗(yàn)
不建議的做法
NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16]};
NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
推薦的做法
// 添加一個(gè)NSForegroundColorAttributeName屬性
NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:[UIColor labelColor]};
NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
12.TabBar紅點(diǎn)偏移
如果之前有通過(guò)TabBar上圖片位置來(lái)設(shè)置紅點(diǎn)位置玷过,在iOS13上會(huì)發(fā)現(xiàn)顯示位置都在最左邊去了爽丹。遍歷UITabBarButton的subViews發(fā)現(xiàn)只有在TabBar選中狀態(tài)下才能取到UITabBarSwappableImageView筑煮,解決辦法是修改為通過(guò)UITabBarButton的位置來(lái)設(shè)置紅點(diǎn)的frame
13.廢棄UIWebView(必須)
UIWebView在12.0就已經(jīng)被廢棄,部分APP使用webview時(shí), 審核被拒
14.WKWebView 中測(cè)量頁(yè)面內(nèi)容高度的方式變更
iOS 13以前 document.body.scrollHeight iOS 13中 document.documentElement.scrollHeight 兩者相差55 應(yīng)該是瀏覽器定義高度變了
15.使用MJExtension 中處理NSNull的不同(必須)
這個(gè)直接會(huì)導(dǎo)致Crash的在將服務(wù)端數(shù)據(jù)字典轉(zhuǎn)換為模型時(shí)粤蝎,如果遇到服務(wù)端給的數(shù)據(jù)為NSNull時(shí)真仲, mj_JSONObject,其中 class_copyPropertyList方法得到的屬性里初澎,多了一種EFSQLBinding類型的東西秸应,而且屬性數(shù)量也不準(zhǔn)確, 那就沒辦法了碑宴, 我只能改寫這個(gè)方法了软啼,這個(gè)組件沒有更新的情況下,寫了一個(gè)方法swizzling掉把當(dāng)遇到 NSNull時(shí)延柠,直接轉(zhuǎn)為nil了祸挪。
有人問(wèn)這個(gè)方法怎么實(shí)現(xiàn),其實(shí)目前我們項(xiàng)目在ios13下面還沒遇到這種情況贞间,發(fā)一個(gè)之前項(xiàng)目處理null的方法贿条,在項(xiàng)目里面寫一個(gè)NSObject的分類,添加下面的方法就可以了榜跌。大家請(qǐng)根據(jù)項(xiàng)目情況闪唆、數(shù)據(jù)格式修改下這個(gè)方法盅粪,MJ庫(kù)里面自己會(huì)進(jìn)行替換的:
- (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property {
//為了解決json字符串先賦值給oc字典后钓葫,類型轉(zhuǎn)換crash問(wèn)題,如:
//json->oldValue:0
//model中值為NSString類型
//如果先將json轉(zhuǎn)為dic票顾,dic中對(duì)應(yīng)value值為NSNumber類型础浮,則向oldValue發(fā)送isEqualToString消息會(huì)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 有兩種狀態(tài),default 和 lightContent
現(xiàn)在 Status Bar 有三種狀態(tài)奠骄,default, darkContent 和 lightContent
現(xiàn)在的 darkContent 對(duì)應(yīng)之前的 default豆同,現(xiàn)在的 default 會(huì)根據(jù)情況自動(dòng)選擇 darkContent 和 lightContent
17.UIActivityIndicatorView(必須)
之前的 UIActivityIndicatorView 有三種 style 分別為 whiteLarge, white 和 gray,現(xiàn)在全部廢棄含鳞。
增加兩種 style 分別為 medium 和 large影锈,指示器顏色用 color 屬性修改。
18.藍(lán)牙權(quán)限需要申請(qǐng)
CBCentralManager蝉绷,iOS13以前鸭廷,使用藍(lán)牙時(shí)可以直接用,不會(huì)出現(xiàn)權(quán)限提示熔吗,iOS13后辆床,再使用就會(huì)提示了。 在info.plist里增加
<key>NSBluetoothAlwaysUsageDescription</key>
<string>我們要一直使用您的藍(lán)牙</string>
在iOS13中桅狠,藍(lán)牙變成了和位置讼载,通知服務(wù)等同樣的可以針對(duì)單個(gè)app授權(quán)的服務(wù)轿秧。
- (NSString*) getWifiSsid {
if (@available(iOS 13.0, *)) {
//用戶明確拒絕,可以彈窗提示用戶到設(shè)置中手動(dòng)打開權(quán)限
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
NSLog(@"User has explicitly denied authorization for this application, or location services are disabled in Settings.");
//使用下面接口可以打開當(dāng)前應(yīng)用的設(shè)置頁(yè)面
//[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
return nil;
}
CLLocationManager* cllocation = [[CLLocationManager alloc] init];
if(![CLLocationManager locationServicesEnabled] || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined){
//彈框提示用戶是否開啟位置權(quán)限
[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 相關(guān)使用變更
最近收到了蘋果的郵件菇篡,說(shuō)獲取WiFi SSID的接口CNCopyCurrentNetworkInfo 不再返回SSID的值。不仔細(xì)看還真會(huì)被嚇一跳一喘,對(duì)物聯(lián)網(wǎng)的相關(guān)APP簡(jiǎn)直是炸彈逸贾。仔細(xì)看郵件還好說(shuō)明了可以先獲取用戶位置權(quán)限才能返回SSID。
注意:目本身已經(jīng)打開位置權(quán)限津滞,則可以直接獲取
轉(zhuǎn)載請(qǐng)注明出處
參考鏈接铝侵,如有侵權(quán),請(qǐng)告知触徐,
https://github.com/ChenYilong/iOS13AdaptationTips/issues
iOS 13 適配
iOS13適配
適配 iOS13
Xcode11 和 iOS13 適配
iOS13 UI & 功能適配
解決Xcode11-beta版本新創(chuàng)建iOS工程低版本黑屏的問(wèn)題
Modernizing Your UI for iOS13