在簡(jiǎn)書(shū)偶爾會(huì)被鎖定,本文在掘金也同步更新。
iOS 13 支持適配的機(jī)型
- iPhone 11、iPhone 11 Pro、iPhone 11 Pro Max
- 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 (第七代)
- 后續(xù)發(fā)布的新版本 iPhone
適配要求
Starting April, 2020, all iPhone and iPad apps submitted to the App Store will need to be built with the iOS 13 SDK or later. They must also support the all-screen design of iPhone XS Max or the 12.9-inch iPad Pro (3rd generation), or later.
根據(jù)官網(wǎng)的說(shuō)法,2020年4月之后所有提交到 App Store 的 iPhone 和 iPad 應(yīng)用必須使用 iOS 13 以上的 SDK 進(jìn)行編譯,并支持 iPhone Xs Max 或 12.9 寸 iPad Pro (3代) 及以后版本的全屏幕設(shè)計(jì)。
新特性適配
1. Dark Mode
iOS 13 推出暗黑模式,UIKit
提供新的系統(tǒng)顏色和 api 來(lái)適配不同顏色模式,xcassets
對(duì)素材適配也做了調(diào)整,具體適配可見(jiàn): Implementing Dark Mode on iOS。
如果不打算適配 Dark Mode,可以直接在 Info.plist
中添加一欄:User Interface Style
: Light
,即可在應(yīng)用內(nèi)禁用暗黑模式。不過(guò)即使設(shè)置了顏色方案,申請(qǐng)權(quán)限的系統(tǒng)彈窗還是會(huì)依據(jù)系統(tǒng)的顏色進(jìn)行顯示,自己創(chuàng)建的 UIAlertController
就不會(huì)。
2. Sign In with Apple
在 iOS 13 中蘋(píng)果推出一種在 App 和網(wǎng)站上快速、便捷登錄的方式: Sign In With Apple。這是 iOS 13 新增的功能,因此需要使用 Xcode 11 進(jìn)行開(kāi)發(fā)。關(guān)于應(yīng)用是否要求接入此登錄方式,蘋(píng)果在 App Store 應(yīng)用審核指南 中提到:
Apps that exclusively use a third-party or social login service (such as Facebook Login, Google Sign-In, Sign in with Twitter, Sign In with LinkedIn, Login with Amazon, or WeChat Login) to set up or authenticate the user’s primary account with the app must also offer Sign in with Apple as an equivalent option.
如果你的應(yīng)用使用了第三方或社交賬號(hào)登錄服務(wù)(如Facebook、Google、Twitter、LinkedIn、Amazon、微信等)來(lái)設(shè)置或驗(yàn)證用戶的主賬號(hào),就必須把 Sign In With Apple
作為同等的選項(xiàng)添加到應(yīng)用上。如果是下面這些類型的應(yīng)用則不需要添加:
- 僅僅使用公司內(nèi)部賬號(hào)來(lái)注冊(cè)和登錄的應(yīng)用;
- 要求用戶使用現(xiàn)有的教育或企業(yè)賬號(hào)進(jìn)行登錄的教育、企業(yè)或商務(wù)類型的應(yīng)用;
- 使用政府或業(yè)界支持的公民身份識(shí)別系統(tǒng)或電子標(biāo)識(shí)對(duì)用戶進(jìn)行身份驗(yàn)證的應(yīng)用;
- 特定第三方服務(wù)的應(yīng)用,用戶需要直接登錄其郵箱、社交媒體或其他第三方帳戶才能訪問(wèn)其內(nèi)容。
另外需要注意,關(guān)于何時(shí)要求接入 Sign In With Apple
,蘋(píng)果在 News and Updates 中提到:
Starting today, new apps submitted to the App Store must follow these guidelines. Existing apps and app updates must follow them by April 2020.
2019 年 9 月 12 日 起,提交到 App Store 的新應(yīng)用必須按照應(yīng)用審核指南中的標(biāo)準(zhǔn)進(jìn)行接入;現(xiàn)有應(yīng)用和應(yīng)用更新必須也在 2020 年 4 月前完成接入。
API 適配
1. 私有方法 KVC 可能導(dǎo)致崩潰
在 iOS 13 中部分方法屬性不允許使用 valueForKey
、setValue:forKey:
來(lái)獲取或者設(shè)置私有屬性,具體表現(xiàn)為在運(yùn)行時(shí)會(huì)直接崩潰,并提示以下崩潰信息:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Access to UISearchBar's _searchField ivar is prohibited. This is an application bug'
目前整理的會(huì)導(dǎo)致崩潰的私有 api 和對(duì)應(yīng)替代方案如下,感謝 @君賞 的反饋,也歡迎各位大佬補(bǔ)充和指正 :
// 崩潰 api
UITextField *textField = [searchBar valueForKey:@"_searchField"];
// 替代方案 1,使用 iOS 13 的新屬性 searchTextField
searchBar.searchTextField.placeholder = @"search";
// 替代方案 2,遍歷獲取指定類型的屬性
- (UIView *)findViewWithClassName:(NSString *)className inView:(UIView *)view{
Class specificView = NSClassFromString(className);
if ([view isKindOfClass:specificView]) {
return view;
}
if (view.subviews.count > 0) {
for (UIView *subView in view.subviews) {
UIView *targetView = [self findViewWithClassName:className inView:subView];
if (targetView != nil) {
return targetView;
}
}
}
return nil;
}
// 調(diào)用方法
UITextField *textField = [self findViewWithClassName:@"UITextField" inView:_searchBar];
// 崩潰 api
[searchBar setValue:@"取消" forKey:@"_cancelButtonText"];
// 替代方案,用同上的方法找到子類中 UIButton 類型的屬性,然后設(shè)置其標(biāo)題
UIButton *cancelButton = [self findViewWithClassName:NSStringFromClass([UIButton class]) inView:searchBar];
[cancelButton setTitle:@"取消" forState:UIControlStateNormal];
// 崩潰 api。獲取 _placeholderLabel 不會(huì)崩潰,但是獲取 _placeholderLabel 里的屬性就會(huì)
[textField setValue:[UIColor blueColor] forKeyPath:@"_placeholderLabel.textColor"];
[textField setValue:[UIFont systemFontOfSize:20] forKeyPath:@"_placeholderLabel.font"];
// 替代方案 1,去掉下劃線,訪問(wèn) placeholderLabel
[textField setValue:[UIColor blueColor] forKeyPath:@"placeholderLabel.textColor"];
[textField setValue:[UIFont systemFontOfSize:20] forKeyPath:@"placeholderLabel.font"];
// 替代方案 2
textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"輸入" attributes:@{
NSForegroundColorAttributeName: [UIColor blueColor],
NSFontAttributeName: [UIFont systemFontOfSize:20]
}];
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.模態(tài)視圖的默認(rèn)樣式發(fā)生改變
在 iOS 13,使用 presentViewController
方式打開(kāi)模態(tài)視圖,默認(rèn)的如下圖所示的視差效果,通過(guò)下滑返回。
這是因?yàn)樘O(píng)果將 UIViewController
的 modalPresentationStyle
屬性的默認(rèn)值改成了新加的一個(gè)枚舉值 UIModalPresentationAutomatic,對(duì)于多數(shù) UIViewController
,此值會(huì)映射成 UIModalPresentationPageSheet
。
需要注意,這種效果彈出來(lái)的頁(yè)面導(dǎo)航欄部分是會(huì)被砍掉的,在 storyboard 中也可以看到,頁(yè)面布局時(shí)需要注意導(dǎo)航欄的內(nèi)容不要被遮擋。
還有一點(diǎn)注意的是,我們?cè)瓉?lái)以全屏的樣式彈出一個(gè)頁(yè)面,那么將這個(gè)頁(yè)面彈出的那個(gè) ViewController 會(huì)依次調(diào)用 viewWillDisappear
和 viewDidDisappear
。然后在這個(gè)頁(yè)面被 dismiss 的時(shí)候,將他彈出的那個(gè) ViewController 的 viewWillAppear
和 viewDidAppear
會(huì)被依次調(diào)用。然而使用默認(rèn)的視差效果彈出頁(yè)面,將他彈出的那個(gè) ViewController 并不會(huì)調(diào)用這些方法,原先寫(xiě)在這四個(gè)函數(shù)中的代碼以后都有可能會(huì)存在問(wèn)題。
解決方案
如果視差效果的樣式可以接受的話,就不需要修改;如果需要改回全屏顯示的界面,需要手動(dòng)設(shè)置彈出樣式:
- (UIModalPresentationStyle)modalPresentationStyle {
return UIModalPresentationFullScreen;
}
4. UISearchBar 黑線處理導(dǎo)致崩潰
之前為了處理搜索框的黑線問(wèn)題,通常會(huì)遍歷 searchBar 的 subViews,找到并刪除 UISearchBarBackground
。
for (UIView *view in _searchBar.subviews.lastObject.subviews) {
if ([view isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) {
[view removeFromSuperview];
break;
}
}
在 iOS13 中這么做會(huì)導(dǎo)致 UI 渲染失敗,然后直接崩潰,崩潰信息如下:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Missing or detached view for search bar layout'
解決方案
設(shè)置 UISearchBar
的背景圖片為空:
[_searchBar setBackgroundImage:[UIImage new]];
5. UITabBarButton 不同狀態(tài)下結(jié)構(gòu)不同
在 iOS 13 中,UITabBarButton
的控件結(jié)構(gòu)會(huì)隨著其選中狀態(tài)的變化而變化,主要體現(xiàn)為 UITabBarSwappableImageView
和 UITabBarButtonLabel
的位置變化。在選中時(shí)和以前一樣,是 UITabBarButton
的子控件。而在未選中狀態(tài)下放到了 UIVisualEffectView
的 _UIVisualEffectContentView
里面。感謝@關(guān)燈俠的提醒,具體可以看下圖的對(duì)比:
我們?cè)谧远x UITabBar
時(shí),通常會(huì)遍歷 UITabBarButton
的子控件獲取 UITabBarSwappableImageView
,比如自定義紅點(diǎn)時(shí)添加到這個(gè) ImageView 的右上角,這在 iOS 13 中可能就會(huì)導(dǎo)致異常。
解決方案
可以使用遞歸遍歷 UITabBarButton
的所有 subviews 獲取 UITabBarSwappableImageView
,具體可以參照上面 私有方法 KVC 可能導(dǎo)致崩潰 章節(jié)中給出的遞歸遍歷方法。
另外需要注意,未選中狀態(tài)下,添加的紅點(diǎn)會(huì)和 tabBar 的圖片一樣變成灰色,這一點(diǎn)應(yīng)該也是因?yàn)槠浣Y(jié)構(gòu)變化造成的。具體可以見(jiàn)下圖:
如果想要和以前一樣未選中時(shí)也是紅色,也很簡(jiǎn)單,把紅點(diǎn)添加到 UITabBarButton
上,位置再根據(jù) UITabBarSwappableImageView
調(diào)整即可。
6. UINavigationBar 設(shè)置按鈕邊距導(dǎo)致崩潰
從 iOS 11 開(kāi)始,UINavigationBar
使用了自動(dòng)布局,左右兩邊的按鈕到屏幕之間會(huì)有 16 或 20 的邊距。
為了避免點(diǎn)擊到間距的空白處沒(méi)有響應(yīng),通常做法是:定義一個(gè)
UINavigationBar
子類,重寫(xiě) layoutSubviews
方法,在此方法里遍歷 subviews 獲取 _UINavigationBarContentView
,并將其 layoutMargins
設(shè)置為 UIEdgeInsetsZero
。
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@"_UINavigationBarContentView"]) {
subview.layoutMargins = UIEdgeInsetsZero;
break;
}
}
}
然而,這種做法在 iOS 13 中會(huì)導(dǎo)致崩潰,崩潰信息如下:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Client error attempting to change layout margins of a private view'
解決方案
使用設(shè)置 frame 的方式,讓 _UINavigationBarContentView
向兩邊伸展,從而抵消兩邊的邊距。
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@"_UINavigationBarContentView"]) {
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
UIEdgeInsets margins = subview.layoutMargins;
subview.frame = CGRectMake(-margins.left, -margins.top, margins.left + margins.right + subview.frame.size.width, margins.top + margins.bottom + subview.frame.size.height);
} else {
subview.layoutMargins = UIEdgeInsetsZero;
}
break;
}
}
}
7. 子線程修改界面導(dǎo)致崩潰(相冊(cè)首次授權(quán)回調(diào)必現(xiàn))
在使用相冊(cè)時(shí)我們會(huì)調(diào)用 [PHPhotoLibrary requestAuthorization:]
方法獲取權(quán)限,獲取的結(jié)果會(huì)通過(guò)一個(gè)帶有 PHAuthorizationStatus
信息的 block 進(jìn)行回調(diào)。
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
// 根據(jù) status 判斷不同狀態(tài)
}];
根據(jù) @路隨心生 的反饋,在 iOS 13 中,如果在第一次獲取權(quán)限的回調(diào)中直接修改界面,會(huì)導(dǎo)致崩潰,崩潰信息如下:
This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'
實(shí)際測(cè)試,第一次授權(quán)崩潰必先,再次授權(quán)偶現(xiàn)。另外,正如崩潰信息所言,不只是相冊(cè)授權(quán)回調(diào)線程,其他子線程修改界面都有一定概率導(dǎo)致崩潰,而在 iOS 13 中貌似概率更高。
解決方案
在 Xcode 中調(diào)試運(yùn)行時(shí),子線程修改界面會(huì)有紫色感嘆號(hào)標(biāo)出,注意修改成回到主線程即可。
8. WKWebView 在 iPad 上默認(rèn)顯示桌面版的網(wǎng)頁(yè)
在 iPadOS 上,使用 WKWebView
打開(kāi)網(wǎng)頁(yè)默認(rèn)使用桌面版的網(wǎng)頁(yè),抓包發(fā)現(xiàn)其默認(rèn)的 UserAgent 變成了 Desktop 版本:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko)
原因是蘋(píng)果在 iOS 13.0 中為 WKWebView
添加了切換桌面版本和手機(jī)版本的方法,其通過(guò)在一個(gè)新加的枚舉值控制:
typedef NS_ENUM(NSInteger, WKContentMode) {
WKContentModeRecommended,
WKContentModeMobile,
WKContentModeDesktop
} API_AVAILABLE(ios(13.0));
此枚舉默認(rèn)值為 WKContentModeRecommended
,在 iPhone 和 iPad mini 上映射為 WKContentModeMobile
,在其他 iPad 上則為 WKContentModeDesktop
,因此 iPad 上打開(kāi)網(wǎng)頁(yè)默認(rèn)顯示桌面版本。
解決方案
- 可通過(guò)
WKWebViewConfiguration
的新屬性 defaultWebpagePreferences 來(lái)設(shè)置,目前其僅包含一個(gè)WKContentMode
類型的屬性 preferredContentMode,默認(rèn)值為WKContentModeRecommended
可以通過(guò)改變其值來(lái)修改顯示的版本:
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
if (@available(iOS 13.0, *)) {
configuration.defaultWebpagePreferences.preferredContentMode = WKContentModeMobile;
}
- 除了在初始化時(shí)候設(shè)置外,還可以通過(guò)新的代理方法實(shí)現(xiàn)桌面版和移動(dòng)版切換,蘋(píng)果給出了一個(gè)具體的例子: Viewing Desktop or Mobile Web Content Using a Web View,其中關(guān)鍵的方法為:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction preferences:(WKWebpagePreferences *)preferences decisionHandler:(void (^)(WKNavigationActionPolicy, WKWebpagePreferences * _Nonnull))decisionHandler {
preferences.preferredContentMode = WKContentModeMobile;
decisionHandler(WKNavigationActionPolicyAllow, preferences);
}
方法棄用
1. UIWebView 將被禁止提交審核
在 iOS 13 推出后,蘋(píng)果在 UIWebView 的說(shuō)明上將其支持的系統(tǒng)范圍定格在了 iOS 2 ~ iOS 12。在 2019 年 12 月 23 日更新的 News and Updates中,蘋(píng)果給出了確切的時(shí)間節(jié)點(diǎn):
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 月開(kāi)始不再接受包含 UIWebView 的新應(yīng)用提交,2020 年 12 月開(kāi)始不再接受包含 UIWebView 的應(yīng)用更新提交。
解決方案
用 WKWebView
替代 UIWebView
,確保所有 UIWebView
的 api 都要移除,如果需要適配 iOS 7 的可以通過(guò) openURL
的方式在 Safari
打開(kāi)。
2. 使用 UISearchDisplayController 導(dǎo)致崩潰
在 iOS 8 之前,我們?cè)?UITableView
上添加搜索框需要使用 UISearchBar
+ UISearchDisplayController
的組合方式,而在 iOS 8 之后,蘋(píng)果就已經(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.'
解決方案
使用 UISearchController
替換 UISearchBar
+ UISearchDisplayController
的組合方案。
3. MPMoviePlayerController 被棄用
在 iOS 9 之前播放視頻可以使用 MediaPlayer.framework
中的MPMoviePlayerController類來(lái)完成,它支持本地視頻和網(wǎng)絡(luò)視頻播放。但是在 iOS 9 開(kāi)始被棄用,如果在 iOS 13 中繼續(xù)使用的話會(huì)直接拋出異常:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
解決方案
使用 AVFoundation
里的 AVPlayer
作為視頻播放控件。
工程適配
1. 藍(lán)牙權(quán)限字段更新導(dǎo)致崩潰以及提交審核失敗
在 iOS 13 中,蘋(píng)果將原來(lái)藍(lán)牙申請(qǐng)權(quán)限用的 NSBluetoothPeripheralUsageDescription 字段,替換為 NSBluetoothAlwaysUsageDescription 字段。
For apps with a deployment target of iOS 13 and later, use NSBluetoothAlwaysUsageDescription instead.
感謝 @dengChaoJie 的反饋,如果在 iOS 13 中使用舊的權(quán)限字段獲取藍(lán)牙權(quán)限,會(huì)導(dǎo)致崩潰,崩潰信息如下:
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.
另外,如果將沒(méi)有新字段的包提交審核,將會(huì)收到包含 ITMS-90683
的郵件,并提示審核不通過(guò)。
Dear Developer,
We identified one or more issues with a recent delivery for your app, "xxx". Please correct the following issues, then upload again.
ITMS-90683: Missing Purpose String in Info.plist - Your app's code references one or more APIs that access sensitive user data. The app's Info.plist file should contain a NSBluetoothAlwaysUsageDescription key with a user-facing purpose string explaining clearly and completely why your app needs the data. Starting Spring 2019, all apps submitted to the App Store that access user data are required to include a purpose string. If you're using external libraries or SDKs, they may reference APIs that require a purpose string. While your app might not use these APIs, a purpose string is still required. You can contact the developer of the library or SDK and request they release a version of their code that doesn't contain the APIs. Learn more (https://developer.apple.com/documentation/uikit/core_app/protecting_the_user_s_privacy).
Best regards,
The App Store Team
解決方案
官網(wǎng)文檔也有說(shuō)明,就是在 Info.plist 中把兩個(gè)字段都加上。
For deployment targets earlier than iOS 13, add both NSBluetoothAlwaysUsageDescription and NSBluetoothPeripheralUsageDescription to your app’s Information Property List file.
2. CNCopyCurrentNetworkInfo 使用要求更嚴(yán)格
從 iOS 12 開(kāi)始,CNCopyCurrentNetworkInfo
函數(shù)需要開(kāi)啟 Access WiFi Information 的功能后才會(huì)返回正確的值。在 iOS 13 中,這個(gè)函數(shù)的使用要求變得更嚴(yán)格,根據(jù) CNCopyCurrentNetworkInfo 文檔說(shuō)明,應(yīng)用還需要符合下列三項(xiàng)條件中的至少一項(xiàng)才能得到正確的值:
- 使用 Core Location 的應(yīng)用, 并獲得定位服務(wù)權(quán)限。
- 使用 NEHotspotConfiguration 來(lái)配置 WiFi 網(wǎng)絡(luò)的應(yīng)用。
- 目前正處于啟用狀態(tài)的 VPN 應(yīng)用。
蘋(píng)果作出這項(xiàng)改變主要為了保障用戶的安全,因?yàn)楦鶕?jù) MAC 地址容易推算出用戶當(dāng)前所處的地理位置。同樣,藍(lán)牙設(shè)備也具有 MAC 地址,所以蘋(píng)果也為藍(lán)牙添加了新的權(quán)限,可見(jiàn)上一點(diǎn)。
解決方案
根據(jù)應(yīng)用需求,添加三項(xiàng)要求其中一項(xiàng)。可以選擇第一項(xiàng)獲取定位權(quán)限,因?yàn)樘砑拥某杀静粫?huì)太大,只需要用戶允許應(yīng)用使用定位服務(wù)即可。
3. LaunchImage 被棄用
iOS 8 之前我們是在LaunchImage
來(lái)設(shè)置啟動(dòng)圖,每當(dāng)蘋(píng)果推出新的屏幕尺寸的設(shè)備,我們需要 assets 里面放入對(duì)應(yīng)的尺寸的啟動(dòng)圖,這是非常繁瑣的一個(gè)步驟。因此在 iOS 8 蘋(píng)果引入了 LaunchScreen
,可以直接在 Storyboard 上設(shè)置啟動(dòng)界面樣式,可以很方便適配各種屏幕。
需要注意的是,蘋(píng)果在 Modernizing Your UI for iOS 13 section 中提到
,從2020年4月開(kāi)始,所有支持 iOS 13 的 App 必須提供 LaunchScreen.storyboard
,否則將無(wú)法提交到 App Store 進(jìn)行審批。
解決方案
使用 LaunchScreen.storyboard
設(shè)置啟動(dòng)頁(yè),棄用 LaunchImage
。
4. UISegmentedControl 默認(rèn)樣式改變
默認(rèn)樣式變?yōu)榘椎缀谧郑绻O(shè)置修改過(guò)顏色的話,頁(yè)面需要修改。
原本設(shè)置選中顏色的 tintColor
已經(jīng)失效,新增了 selectedSegmentTintColor 屬性用以修改選中的顏色。
5. 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
中管理,但是舊版本根本沒(méi)有 UIScene
。
解決方案
在 AppDelegate
的頭文件加上:
@property (strong, nonatomic) UIWindow *window;
6. 默認(rèn)彈出樣式打開(kāi)的頁(yè)面在 WKWebView 中獲取照片崩潰
由于 iOS 13 中模態(tài)視圖的默認(rèn)樣式發(fā)生改變,如果以默認(rèn)的 UIModalPresentationPageSheet
樣式彈出一個(gè) ViewController,并使用 WKWebView
通過(guò) HTML 獲取系統(tǒng)照片:
[_webView loadHTMLString:@"<input accept='image/*' type='file'>" baseURL:nil];
在點(diǎn)擊選擇按鈕時(shí),根據(jù)@傷心的Easyman的反饋,會(huì)出現(xiàn)崩潰,崩潰信息如下:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIDocumentMenuViewController (<UIDocumentMenuViewController: 0x101226860>). In its current trait environment, the modalPresentationStyle of a UIDocumentMenuViewController with this style is UIModalPresentationPopover. You must provide location information for this popover through the view controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the view controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.'
具體原因是,點(diǎn)擊獲取系統(tǒng)照片時(shí),會(huì)彈出一個(gè)模態(tài)視圖的樣式為 UIModalPresentationPopover
的 UIDocumentMenuViewController
,這種樣式下,如果其父 UIViewController
以非全屏方式 present 的,那么就需要像 iPad 一樣指定其 sourceView
和 sourceRect
,或者指定一個(gè) barButtonItem
,否則會(huì)出現(xiàn)上述崩潰。而使用 UIModalPresentationFullScreen
的方式彈出的話就不會(huì)有這個(gè)問(wèn)題。
解決方案
第一種方法就是指定sourceView
、sourceRect
,barButtonItem
同理:
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
[self setUIDocumentMenuViewControllerSoureViewsIfNeeded:viewControllerToPresent];
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
- (void)setUIDocumentMenuViewControllerSoureViewsIfNeeded:(UIViewController *)viewControllerToPresent{
if (@available(iOS 13, *)) {
if([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone && [viewControllerToPresent isKindOfClass:UIDocumentMenuViewController.class]){
viewControllerToPresent.popoverPresentationController.sourceView = self.webView;
viewControllerToPresent.popoverPresentationController.sourceRect = CGRectMake(15, 5, 1, 1); // 具體看按鈕的位置
}
}
}
// 如果頂層有 UINavigationController 的話,需要如下指定
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
if([self.viewControllers.lastObject isKindOfClass:WKWebViewController.class]){
WKWebViewController *vc = self.viewControllers.lastObject;
[vc setUIDocumentMenuViewControllerSoureViewsIfNeeded:viewControllerToPresent];
}
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
第二種方法就是使用全屏的方式彈出(實(shí)踐證明默認(rèn)彈出樣式在橫屏下是全屏的不會(huì)崩)
- (UIModalPresentationStyle)modalPresentationStyle {
return UIModalPresentationFullScreen;
}
SDK 適配
1. 使用 @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
方法沒(méi)有具體的實(shí)現(xiàn),但是工程里根本沒(méi)有這個(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,那么需要避開(kāi)此方法,通過(guò)獲取系統(tǒng)版本來(lái)進(jìn)行判斷:
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
...
}
另外,在 Xcode 10 上打開(kāi) 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
參考文章
本文結(jié)合個(gè)人遇到的問(wèn)題和以下文章部分內(nèi)容,對(duì)常見(jiàn)適配問(wèn)題進(jìn)行總結(jié)