iOS 13適配總結

iOS 13 支持適配的機型

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層面

注:必須適配的點以“(必須)”標出,這個必須有的點是一定要做的,有的是看個人項目,大家可忽略

1.Dark Mode

iOS 13 推出暗黑模式,UIKit 提供新的系統顏色和 api 來適配不同顏色模式,xcassets 對素材適配也做了調整,官方具體適配可見: Implementing Dark Mode on iOS
適配方案:

a.png

參考鏈接:
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.
如果你的應用支持使用第三方登錄,那么就必須加上蘋果新推出的登錄方式:Introducing Sign In with Apple。目前蘋果只在 News and Updates 上提到正式發布時要求加上,具體發布時間還沒確定。

3.模態彈出默認交互改變(必須)

在 iOS 13 中此枚舉值直接成為了模態彈出的默認值,因此 presentViewController 方式打開視圖是如下的視差效果,默認是下滑返回。


b.gif

iOS13下仍然可以做到全屏彈出,這里需要UI決定采用哪種樣式

4.UISegmentedControl 默認樣式改變(必須)

默認樣式變為白底黑字,如果設置修改過顏色的話,頁面需要修改。


c.png

原本設置選中顏色的 tintColor 已經失效,新增了 selectedSegmentTintColor 屬性用以修改選中的顏色。
Web Content適配

5.h5的適配,參考鏈接:

https://blog.csdn.net/u012413955/article/details/92198556

二、代碼層面

1.私有方法 KVC 不允許使用(必須)

在 iOS 13 中不再允許使用 valueForKey、setValue:forKey: 等方法獲取或設置私有屬性,雖然編譯可以通過,但是在運行時會直接崩潰,并提示一下崩潰信息:

// 使用的私有方法
[_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.


d.png

這是為了 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:


xxx.png

解決方法:嵌套一個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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容