iOS13適配更新總結(jié)

前言:

iOS13的API的變動和適配問題,我從新特性適配、API 適配、方法棄用、工程適配、SDK 適配、其他問題分類歸納整理。

新特性適配
1.暗黑模式Dark Mode
2.蘋果登錄 Sign In with Apple

API 適配
1.模態(tài)視圖彈出方式改變
2.私有方法 KVC 被禁用
3.searchBar設(shè)置textField問題
4.UISearchBar 黑線處理導(dǎo)致崩潰
5.推送的 deviceToken 獲取到的格式發(fā)生變化
6.UISegmentedControl默認樣式改變

方法棄用
1.UIWebView 將被禁止提交審核
2.使用 UISearchDisplayController 導(dǎo)致崩潰
3.MPMoviePlayerController 被棄用

工程適配
1.LaunchImage 被棄用
2.iOS13 新生命周期 SceneDelegate
3.藍牙權(quán)限字段更新導(dǎo)致崩潰以及提交審核失敗
4.CNCopyCurrentNetworkInfo 使用要求更嚴格
5.修改APP名稱(修改DisplayName值)

SDK 適配
1.使用 @available 導(dǎo)致舊版本 Xcode 編譯出錯。

其他問題
1.[_LSDefaults sharedInstance]崩潰

一、新特性適配

1.暗黑模式Dark Mode

iOS 13 推出暗黑模式,UIView默認背景色會變成暗黑色。適配暗黑模式的工作量較大,改為強制使用正常模式。

處理方案:在plist文件中增加配置項UIUserInterfaceStyle,值為Light。

2.蘋果登錄 Sign In with Apple

關(guān)于蘋果登錄,如果應(yīng)用沒有使用第三方登錄,可以不用添加,如果 APP 支持三方登陸(Facbook、Google、微信、QQ、支付寶、微博等),就必須支持蘋果登陸,且要放前邊。

建議支持使用Apple提供的按鈕樣式,已經(jīng)適配各類設(shè)備。

2019 年 9 月 12 日 起,提交到 App Store 的新應(yīng)用必須按照應(yīng)用審核指南中的標準進行接入;現(xiàn)有應(yīng)用和應(yīng)用更新必須也在 2020 年 4 月前完成接入。

二、API 適配

1. 模態(tài)視圖彈出方式改變

在 iOS 13 UIModalPresentationStyle 枚舉的定義中,蘋果新加了一個枚舉值:

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

新的交互方式默認為下拉dismiss,且導(dǎo)航欄會留白空出;
如果需要點擊取消按鈕才消失界面的,需要適配;
如果你完全接受蘋果的這個默認效果,那就不需要去修改任何代碼。
如果需要做成全屏顯示的界面,需要手動設(shè)置彈出樣式:

- (UIModalPresentationStyle)modalPresentationStyle {
    return UIModalPresentationFullScreen;
}

如果,之前代碼已經(jīng)設(shè)置了modalPresentationStyle的值,那你也不會有這個影響。對于想要找回原來默認交互的同學(xué),直接設(shè)置如下即可:

在presentViewController之前加入這個代碼:

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

nav.modalPresentationStyle = UIModalPresentationFullScreen;

2.私有方法 KVC 被禁用

iOS13后不再允許valueForKey、setValue:forKey: 等方法獲取或設(shè)置私有屬性,雖然編譯可以通過,但是在運行時會直接崩潰或不起作用。

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

解決方案一:富文本設(shè)置

UITextField有一個attributedPlaceholder屬性,占位符富文本,和UILabel等控件的富文本設(shè)置一樣,可以設(shè)置文字顏色,尺寸等。

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設(shè)置textField問題

我們不用再用kvc獲取UISearchBar的textField了,因為,iOS13中系統(tǒng)將searchTextField屬性暴露出來了,不再是私有屬性了,你可以直接調(diào)用。

UITextField *searchField = _searchBar.searchTextField;

注意:以上寫法在iOS13以下手機運行會崩潰,所以,暫時要寫成兩種情況的寫法,iOS13依然沿用之前的舊寫法。
[[[UIDevice currentDevice] systemVersion] doubleValue] >= 13.0加以區(qū)分

4.UISearchBar 黑線處理導(dǎo)致崩潰

之前為了處理搜索框的黑線問題,通常會遍歷 searchBar 的 subViews,找到并刪除 UISearchBarBackground,在 iOS13 中這么做會導(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;
    }

5. 推送的 deviceToken 獲取到的格式發(fā)生變化

原本可以直接將 NSData 類型的 deviceToken 轉(zhuǎn)換成 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 中,這種方法已經(jīng)失效,NSData類型的 deviceToken 轉(zhuǎn)換成的字符串變成了:

{length = 32, bytes = 0xd7f9fe34 69be14d1 fa51be22 329ac80d ... 5ad13017 b8ad0736 }

需要進行一次數(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);
}

6. UISegmentedControl默認樣式改變

默認樣式變?yōu)榘椎缀谧?,如果設(shè)置修改過顏色的話,頁面需要修改。

原本設(shè)置選中顏色的 tintColor 已經(jīng)失效,新增了 selectedSegmentTintColor 屬性用以修改選中的顏色。

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

iOS12以前設(shè)置圓角:

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

但是iOS13系統(tǒng)下,完全不起作用,后來找到了方法,可以用重寫UISegmentedControl的layoutSubviews方法來設(shè)置圓角。

class TestSegmentedControl: UISegmentedControl {

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

}

這樣子我們用TestSegmentedControl作為子類重寫父類的layoutSubviews的方法,就又可以設(shè)置圓角啦。

如果項目有需求一定要在iOS13系統(tǒng)上實現(xiàn)iOS12以前的風格樣式,可以自定義一個category來實現(xiàn):

@interface UISegmentedControl (Style_OC)

/// UISegmentedControl 將iOS13風格轉(zhuǎn)化成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 的說明上將其支持的系統(tǒng)范圍定格在了 iOS 2 ~ iOS 12。目前,如果開發(fā)者將包含 UIWebView api 的應(yīng)用更新上傳到 App Store 審核后,其將會收到包含 ITMS-90809 信息的回復(fù)郵件,提示你在下一次提交時將應(yīng)用中 UIWebView 的 api 移除。

解決方案:
WKWebView 替代 UIWebView,確保所有 UIWebView 的 api 都要移除,如果需要適配 iOS 7 的可以通過 openURL 的方式在 Safari 打開。

2. 使用 UISearchDisplayController 導(dǎo)致崩潰

在 iOS 8 之前,我們在 UITableView 上添加搜索框需要使用 UISearchBar + UISearchDisplayController 的組合方式,而在 iOS 8 之后,蘋果就已經(jīng)推出了 UISearchController 來代替這個組合方式。在 iOS 13 中,如果還繼續(xù)使用 UISearchDisplayController 會直接導(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.' 

另外說一下,在 iOS 13 中終于可以獲取直接獲取搜索的文本框:

_searchBar.searchTextField.text = @"search";

3.MPMoviePlayerController 被棄用

在 iOS 9 之前播放視頻可以使用 MediaPlayer.framework 中的MPMoviePlayerController類來完成,它支持本地視頻和網(wǎng)絡(luò)視頻播放。但是在 iOS 9 開始被棄用,如果在 iOS 13 中繼續(xù)使用的話會直接拋出異常:

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

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

四、工程適配

1.LaunchImage 被棄用

iOS 8 之前我們是在LaunchImage 來設(shè)置啟動圖,但是隨著蘋果設(shè)備尺寸越來越多,我們需要在對應(yīng)的 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 設(shè)置啟動頁,棄用 LaunchImage。

2. iOS13 新生命周期 SceneDelegate

Xcode 11 創(chuàng)建的工程在低版本設(shè)備上運行黑屏
使用 Xcode 11 創(chuàng)建的工程,運行設(shè)備選擇 iOS 13.0 以下的設(shè)備,運行應(yīng)用時會出現(xiàn)黑屏。這是因為 Xcode 11 默認是會創(chuàng)建通過 UIScene 管理多個 UIWindow 的應(yīng)用,工程中除了 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. 藍牙權(quán)限字段更新導(dǎo)致崩潰以及提交審核失敗

在 iOS 13 中,蘋果將原來藍牙申請權(quán)限用的 NSBluetoothPeripheralUsageDescription 字段,替換為 NSBluetoothAlwaysUsageDescription 字段。
如果在 iOS 13 中使用舊的權(quán)限字段獲取藍牙權(quán)限,會導(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.

另外,如果將沒有新字段的包提交審核,將會收到包含 ITMS-90683 的郵件,并提示審核不通過。

解決方案

官網(wǎng)文檔也有說明,就是在 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 函數(shù)需要開啟 Access WiFi Information 的功能后才會返回正確的值。在 iOS 13 中,這個函數(shù)的使用要求變得更嚴格,根據(jù) CNCopyCurrentNetworkInfo 文檔說明,應(yīng)用還需要符合下列三項條件中的至少一項才能得到正確的值:

  • 使用 Core Location 的應(yīng)用, 并獲得定位服務(wù)權(quán)限。
  • 使用 NEHotspotConfiguration 來配置 WiFi 網(wǎng)絡(luò)的應(yīng)用。
  • 目前正處于啟用狀態(tài)的 VPN 應(yīng)用。

蘋果作出這項改變主要為了保障用戶的安全,因為根據(jù) MAC 地址容易推算出用戶當前所處的地理位置。同樣,藍牙設(shè)備也具有 MAC 地址,所以蘋果也為藍牙添加了新的權(quán)限,可見藍牙上這一點。

解決方案:
根據(jù)應(yīng)用需求,添加三項要求其中一項??梢赃x擇第一項獲取定位權(quán)限,因為添加的成本不會太大,只需要用戶允許應(yīng)用使用定位服務(wù)即可。

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

  • 在Xcode創(chuàng)建項目時默認的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 導(dǎo)致舊版本 Xcode 編譯出錯。

在 Xcode 11 的 SDK 工程的代碼里面使用了 @available 判斷當前系統(tǒng)版本,打出來的包放在 Xcode 10 中編譯,會出現(xiàn)一下錯誤:

Undefine symbols for architecture i386:
    "__isPlatformVersionAtLeast", referenced from:
        ...
ld: symbol(s) not found for architecture i386復(fù)制代碼

從錯誤信息來看,是 __isPlatformVersionAtLeast 方法沒有具體的實現(xiàn),但是工程里根本沒有這個方法。實際測試無論在哪里使用@available ,并使用 Xcode 11 打包成動態(tài)庫或靜態(tài)庫,把打包的庫添加到 Xcode 10 中編譯都會出現(xiàn)這個錯誤,因此可以判斷是 iOS 13 的 @available 的實現(xiàn)中使用了新的 api。

解決方案:
如果你的 SDK 需要適配舊版本的 Xcode,那么需要避開此方法,通過獲取系統(tǒng)版本來進行判斷:

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

另外,在 Xcode 10 上打開 SDK 工程也應(yīng)該可以正常編譯,這就需要加上編譯宏進行處理:

#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的變動和適配問題,在工作中可能會有不同的問題和方式出現(xiàn),會持續(xù)更新呦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容