背景
剛剛過完中秋節,第二天app上線被拒,原因是因為啟動app就會crash,領導大早上給我打電話讓我去公司解決,好吧誰讓人家是領導呢!正好iOS10系統剛剛出來,需要適配iOS10,不然上線還是會拒,所以我果斷升級了xcode8.0。我總結了一些資料,接下來介紹一下iOS適配的一些知識點:
1.證書問題
- 開啟xcode8.0時編譯執行時出現下面問題:
這個問題剛開始估計大家都會碰到也是第一個要解決的問題
這個問題就是一個證書的設定問題,下面看兩張圖
正常我們會在BuildeSettings中設定證書:
相信大家都能看到在Genreal下面會有Siging,沒錯這就是新特性,為了方便使用者來管理,大家可以選擇Automatically manage signing。需要輸入開發者賬號!如果沒有賬號也沒關係,在下面也可以選擇Debug、Realease、inHouse模式下對應的證書也可以!
- 但是 但是 但是 如果你的證書如果是萬用字元型別的,但是你的app包含了比如推送、apple pay、他會報錯提示你未報含xx.id的manteid等等。所以你要生成針對你app的bundleid對應的證書!
2.隱私資料訪問問題
問題出現
- 現在app能執行了,當我開啟相機時突然又crash了,好吧,坑還不少,崩潰日誌如下:
This app has crashed because it attempted to access privacy-sensitive data without a usage description.The app’s Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.
崩潰原因
- 上面崩潰意思試圖訪問安全隱私資料,但是又沒有相應的描述,你必須要包含在info.plist裡包含一個鍵值 NSPhotoLibraryUsageDescription對應的描述。
- 其實上面是iOS10,蘋果加強了對隱私資料的保護,要對隱私資料許可權做一個適配
iOS10呼叫相機,訪問通訊錄,訪問相簿等都要在info.plist中加入許可權訪問描述,不然之前你們的專案涉及到這些許可權的地方就會直接crash掉。
解決辦法
- 只需要在info.plist新增NSContactsUsageDescription的key, value自己隨意填寫就可以,這裡列舉出對應的key(Source Code模式下):
1 |
NSPhotoLibraryUsageDescriptionApp需要您的同意,才能訪問相簿NSCameraUsageDescriptionApp需要您的同意,才能訪問相機NSMicrophoneUsageDescriptionApp需要您的同意,才能訪問麥克風NSLocationUsageDescriptionApp需要您的同意,才能訪問位置NSLocationWhenInUseUsageDescriptionApp需要您的同意,才能在使用期間訪問位置NSLocationAlwaysUsageDescriptionApp需要您的同意,才能始終訪問位置NSCalendarsUsageDescriptionApp需要您的同意,才能訪問日曆NSRemindersUsageDescriptionApp需要您的同意,才能訪問提醒事項NSMotionUsageDescriptionApp需要您的同意,才能訪問運動與健身NSHealthUpdateUsageDescriptionApp需要您的同意,才能訪問健康更新 NSHealthShareUsageDescriptionApp需要您的同意,才能訪問健康分享NSBluetoothPeripheralUsageDescriptionApp需要您的同意,才能訪問藍芽NSAppleMusicUsageDescriptionApp需要您的同意,才能訪問媒體資料庫 |
– 隱私資料 | 對應KEY值 |
---|---|
相簿 | NSPhotoLibraryUsageDescription |
相機 | NSCameraUsageDescription |
麥克風 | NSMicrophoneUsageDescription |
位置 | NSLocationUsageDescription |
在使用期間訪問位置 | NSLocationWhenInUseUsageDescription |
始終訪問位置 | NSLocationAlwaysUsageDescription |
日曆 | NSCalendarsUsageDescription |
提醒事項 | NSRemindersUsageDescription |
運動與健身 | NSMotionUsageDescription |
健康更新 | NSHealthUpdateUsageDescription |
健康分享 | NSHealthShareUsageDescription |
藍芽 | NSBluetoothPeripheralUsageDescription |
媒體資料庫 | NSAppleMusicUsageDescription |
跳轉到app內的隱私資料設定頁面
- 我們知道使用者沒開啟,肯定要提醒使用者去設定開啟的吧,所以我們要跳轉到app的隱私資料介面。如何呼叫呢,我定義一個巨集方便呼叫:
UIKIT_EXTERN NSString *const UIApplicationOpenSettingsURLString NS_AVAILABLE_IOS(8_0);
這個是iOS8.0以後的方法
1 2 3 4 5 6 |
//-----------------------系統許可權設定路徑(iOS8以後適用)--------------------- //url #define SettingURL [NSURL URLWithString:UIApplicationOpenSettingsURLString] //調到設定 #define GoToSetting if([[UIApplication sharedApplication] canOpenURL:SettingURL]) { \ [[UIApplication sharedApplication] openURL:SettingURL];}} |
我們直接呼叫下面程式碼就可以了
1 2 3 4 |
//去設定 if (IOS8_OR_LATER) { GoToSetting; } |
看到評論說跳到藍芽有什麼辦法,我補充一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
1.首先你在info.plist的檔案中新增相應許可權 2.在要跳轉藍芽設定介面新增類#import 並宣告代理 3.設定全域性的控制元件@property (nonatomic, strong)CBCentralManager *testCB; 4.在要跳轉的地方寫(最好不要寫區域性變數,因為你得拿到這個CB來做連線裝置等一些處理。當然你要區域性變數也沒關係,你在代理方法也可以拿到CB做處理。看個人喜好了^_^) if (_testCB) { _testCB = nil; _testCB.delegate = nil; _testCB = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; }else{ _testCB = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; } 5.若要獲得藍芽狀態,實現代理方法 -(void)centralManagerDidUpdateState:(CBCentralManager *)central{ } 這樣如果藍芽關閉會提示"開啟藍芽來允許“xxx”連線到配件" ,這樣點選設定就可以跳到藍芽設定介面了 |
iOS 10 幹掉了所有系統設定的 URL Scheme,這意味著你再也不可能直接跳轉到系統設定頁面(比如 WiFi、蜂窩資料、定位等)。
iOS 10中如下跳到系統的設定方法已經不生效了(如果看到解決辦法再補充,如果你有解決辦法請賜教留下你寶貴的評論…..感謝):
1 2 |
//程式碼失效,謹慎使用 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"prefs:root=WIFI"]]; |
3.系統版本判斷方法失效
- 我們之前的系統版本方法如下
- 當系統版本到iOS10.0的時候 9.0和10.0比較的話是降序而不是升序,這樣會導致iOS10.0是最早的版本,這樣後面要走的iOS10的方法可能都不會走而出現問題
1 2 3 4 5 6 7 |
#define IOS9_OR_LATER ([[[UIDevice currentDevice] systemVersion] compare:@"9.0"] != NSOrderedAscending) #define IOS8_OR_LATER ([[[UIDevice currentDevice] systemVersion] compare:@"8.0"] != NSOrderedAscending) #define IOS7_OR_LATER ([[[UIDevice currentDevice] systemVersion] compare:@"7.0"] != NSOrderedAscending) #define IOS6_OR_LATER ([[[UIDevice currentDevice] systemVersion] compare:@"6.0"] != NSOrderedAscending) |
- 下面這樣也不行它會永遠返回NO,substringToIndex:1在iOS 10 會被檢測成 iOS 1了,
1 |
#define isiOS10 ([[[[UIDevice currentDevice] systemVersion] substringToIndex:1] intValue]>=10) |
- 正確的開啟方式應該是:
1 2 3 4 5 6 7 8 9 |
#define IOS10_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) #define IOS9_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0) #define IOS8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) #define IOS7_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) #define IOS6_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 6.0) |
4.UIColor問題
- 官方文件中說:大多數core開頭的圖形框架和AVFoundation都提高了對擴充套件畫素和寬色域色彩空間的支援.通過圖形堆疊擴充套件這種方式比以往支援廣色域的顯示裝置更加容易。現在對UIKit擴充套件可以在sRGB的色彩空間下工作,效能更好,也可以在更廣泛的色域來搭配sRGB顏色.如果你的專案中是通過低階別的api自己實現圖形處理的,建議使用sRGB,也就是說在專案中使用了RGB轉化顏色的建議轉換為使用sRGB,在UIColor類中新增了兩個api:
1 2 |
+ (UIColor *)colorWithDisplayP3Red:(CGFloat)displayP3Red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_IOS(10_0); - (UIColor *)initWithDisplayP3Red:(CGFloat)displayP3Red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_IOS(10_0); |
我用新老方法測試兩個方法在RGB相同的數值在表現上的區別看下圖:
- 可以看出下面的顏色(sRGB方法)比上面的顏色(RGB方法)顏色更深更明顯。
5.真彩色的顯示
- 真彩色的顯示會根據光感應器來自動的調節達到特定環境下顯示與效能的平衡效果,如果需要這個功能的話,可以在info.plist裡配置(在Source Code模式下):
1 |
UIWhitePointAdaptivityStyle |
它有五種取值,分別是:
1 2 3 4 5 |
UIWhitePointAdaptivityStyleStandard // 標準模式 UIWhitePointAdaptivityStyleReading // 閱讀模式 UIWhitePointAdaptivityStylePhoto // 圖片模式 UIWhitePointAdaptivityStyleVideo // 視訊模式 UIWhitePointAdaptivityStyleStandard // 遊戲模式 |
- 如果你的專案是遊戲類的,就選擇UIWhitePointAdaptivityStyleStandard這個模式,五種模式的顯示效果是從上往下遞減,也就是說如果你的專案是圖片處理類的,你選擇的是閱讀模式,給選擇太好的效果會影響效能.
6.字型變化
- 蘋果的預設字型會隨著iOS系統版本的不同而不同,iOS10中字型變大了。導致了原來的顯示有問題,會造成…的出現。暫時沒有好的解決辦法,需要自己在一個個適配一下!
7.外掛取消
- Xcode8取消了三方外掛的功能,好多教程破解可以繼續使用,但是可能app上線可能會被拒。我們最喜愛的VVDocumenter-Xcode也不能使用了,下面是作者的感謝
看來大神都是謙虛的啊(啥時候能成為大神。我還是洗洗睡吧,夢裡啥都有\^_^)
- 上面也提到了我們可以繼續使用註釋,快捷鍵(⌥ Option + ⌘ Command + / )
8.UIStatusBar的問題
- 在iOS10中,如果還使用以前設定UIStatusBar型別或者控制隱藏還是顯示的方法,會報警告,方法過期,如下圖:
警告中提到從iOS9.0開始就棄用這兩個方法了,需要用
-[UIViewController preferredStatusBarstyle]
-[UIViewController preferredStatusBarHidden]來替換使用,那我們來看看新的替換方法。
- 新技能見下面
1 2 3 4 5 6 7 8 9 10 11 |
#if UIKIT_DEFINE_AS_PROPERTIES @property(nonatomic, readonly) UIStatusBarStyle preferredStatusBarStyle NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarStyleDefault @property(nonatomic, readonly) BOOL prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO // Override to return the type of animation that should be used for status bar changes for this view controller. This currently only affects changes to prefersStatusBarHidden. @property(nonatomic, readonly) UIStatusBarAnimation preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarAnimationFade #else - (UIStatusBarStyle)preferredStatusBarStyle NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarStyleDefault - (BOOL)prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO // Override to return the type of animation that should be used for status bar changes for this view controller. This currently only affects changes to prefersStatusBarHidden. - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarAnimationFade #endif |
- 上面這個新方法在UIViewController.h檔案中,這說明什麼?當然說明這是viewController的屬性和方法了,只需要在viewController裡呼叫修改即可
- UIStatusBarStyle 和 prefersStatusBarHidden這兩個屬性是readonly readonly readonly也就是說我們如果呼叫下面 肯定是報錯的:
1 2 3 |
//這是錯誤的寫法 self.preferredStatusBarStyle = UIStatusBarStyleDefault;和 self.prefersStatusBarHidden = YES; |
- 正確的開啟方式在viewController重寫我們還沒用的新的方法
1 2 3 4 5 6 7 8 |
//這是正確的 - (BOOL)prefersStatusBarHidden{ return YES; } - (UIStatusBarStyle)preferredStatusBarStyle{ return UIStatusBarStyleDefault; } |
9.UITextField(好像作用並不大)
- 在iOS 10 中,UITextField新增了textContentType欄位,是UITextContentType型別,它是一個列舉,作用是可以指定輸入框的型別,以便系統可以分析出使用者的語義.是電話型別就建議一些電話,是地址型別就建議一些地址.可以在#import 檔案中,檢視textContentType欄位,有以下可以選擇的型別:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
UIKIT_EXTERN UITextContentType const UITextContentTypeName NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeNamePrefix NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeGivenName NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeMiddleName NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeFamilyName NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeNameSuffix NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeNickname NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeJobTitle NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeOrganizationName NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeLocation NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeFullStreetAddress NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeStreetAddressLine1 NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeStreetAddressLine2 NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeAddressCity NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeAddressState NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeAddressCityAndState NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeSublocality NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeCountryName NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypePostalCode NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeTelephoneNumber NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeEmailAddress NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeURL NS_AVAILABLE_IOS(10_0); UIKIT_EXTERN UITextContentType const UITextContentTypeCreditCardNumber NS_AVAILABLE_IOS(10_0); |
10.UICollectionViewCell的的優化
- 在iOS 10 之前,UICollectionView上面如果有大量cell,當使用者活動很快的時候,整個UICollectionView的卡頓會很明顯,為什麼會造成這樣的問題,這裡涉及到了iOS 系統的重用機制,當cell準備載入進螢幕的時候,整個cell都已經載入完成,等待在螢幕外面了,也就是整整一行cell都已經載入完畢,這就是造成卡頓的主要原因,專業術語叫做:掉幀.
要想讓使用者感覺不到卡頓,我們的app必須幀率達到60幀/秒,也就是說每幀16毫秒要重新整理一次. - iOS 10 之前UICollectionViewCell的生命週期是這樣的:
- 使用者滑動螢幕,螢幕外有一個cell準備載入進來,把cell從reusr佇列拿出來,然後呼叫prepareForReuse方法,在這個方法裡面,可以重置cell的狀態,載入新的資料;
- 繼續滑動,就會呼叫cellForItemAtIndexPath方法,在這個方法裡面給cell賦值模型,然後返回給系統;
- 當cell馬上進去螢幕的時候,就會呼叫willDisplayCell方法,在這個方法裡面我們還可以修改cell,為進入螢幕做最後的準備工作;
- 執行完willDisplayCell方法後,cell就進去螢幕了.當cell完全離開螢幕以後,會呼叫didEndDisplayingCell方法.
- iOS 10 UICollectionViewCell的生命週期是這樣的:
- 使用者滑動螢幕,螢幕外有一個cell準備載入進來,把cell從reusr佇列拿出來,然後呼叫prepareForReuse方法,在這裡當cell還沒有進去螢幕的時候,就已經提前呼叫這個方法了,對比之前的區別是之前是cell的上邊緣馬上進去螢幕的時候就會呼叫該方法,而iOS 10 提前到cell還在螢幕外面的時候就呼叫;
- 在cellForItemAtIndexPath中建立cell,填充資料,重新整理狀態等操作,相比於之前也提前了;
- 使用者繼續滑動的話,當cell馬上就需要顯示的時候我們再呼叫willDisplayCell方法,原則就是:何時需要顯示,何時再去呼叫willDisplayCell方法;
- 當cell完全離開螢幕以後,會呼叫didEndDisplayingCell方法,跟之前一樣,cell會進入重用佇列.
- 在iOS 10 之前,cell只能從重用佇列裡面取出,再走一遍生命週期,並呼叫cellForItemAtIndexPath建立或者生成一個cell.
- 在iOS 10 中,系統會cell儲存一段時間,也就是說當使用者把cell滑出螢幕以後,如果又滑動回來,cell不用再走一遍生命週期了,只需要呼叫willDisplayCell方法就可以重新出現在螢幕中了.
- iOS 10 中,系統是一個一個載入cell的,二以前是一行一行載入的,這樣就可以提升很多效能;
- iOS 10 新增加的Pre-Fetching預載入
這個是為了降低UICollectionViewCell在載入的時候所花費的時間,在 iOS 10 中,除了資料來源協議和代理協議外,新增加了一個UICollectionViewDataSourcePrefetching協議,這個協議裡面定義了兩個方法:
1 2 3 |
- (void)collectionView:(UICollectionView *)collectionView prefetchItemsAtIndexPaths:(NSArray *)indexPaths NS_AVAILABLE_IOS(10_0); - (void)collectionView:(UICollectionView *)collectionView cancelPrefetchingForItemsAtIndexPaths:(NSArray *)indexPaths NS_AVAILABLE_IOS(10_0); |
- 在ColletionView prefetchItemsAt indexPaths這個方法是非同步預載入資料的,當中的indexPaths陣列是有序的,就是item接收資料的順序;
CollectionView cancelPrefetcingForItemsAt indexPaths這個方法是可選的,可以用來處理在滑動中取消或者降低提前載入資料的優先順序.
注意:這個協議並不能代替之前讀取資料的方法,僅僅是輔助載入資料.
Pre-Fetching預載入對UITableViewCell同樣適用.
11.UIRefreshControl
- 在iOS 10 中, UIRefreshControl可以直接在UICollectionView和UITableView中使用,並且脫離了UITableViewController.現在RefreshControl是UIScrollView的一個屬性.
使用方法:
1 2 3 4 5 6 7 8 9 |
//建立 UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; refreshControl.tintColor = [UIColor redColor]; refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"正在重新整理"]; [refreshControl addTarget:self action:@selector(loadData) forControlEvents:UIControlEventValueChanged]; //開始和停止重新整理 [refreshControl beginRefreshing]; [refreshControl endRefreshing]; |
- 也可以進去標頭檔案檢視
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#import - (instancetype)init; @property (nonatomic, readonly, getter=isRefreshing) BOOL refreshing; @property (null_resettable, nonatomic, strong) UIColor *tintColor; @property (nullable, nonatomic, strong) NSAttributedString *attributedTitle UI_APPEARANCE_SELECTOR; // May be used to indicate to the refreshControl that an external event has initiated the refresh action - (void)beginRefreshing NS_AVAILABLE_IOS(6_0); // Must be explicitly called when the refreshing has completed - (void)endRefreshing NS_AVAILABLE_IOS(6_0); |
12.UserNotifications(使用者通知)
- iOS 10 中將通知相關的 API 都統一了,蘋果對這是做了重大改進,變的非常易用。
iOS 9 以前的通知
在呼叫方法時,有些方法讓人很難區分,容易寫錯方法,這讓開發者有時候很苦惱。
應用在執行時和非執行時捕獲通知的路徑還不一致。
應用在前臺時,是無法直接顯示遠端通知,還需要進一步處理。
已經發出的通知是不能更新的,內容發出時是不能改變的,並且只有簡單文字展示方式,擴充套件性根本不是很好。iOS 10 開始的通知
所有相關通知被統一到了UserNotifications.framework框架中。
增加了撤銷、更新、中途還可以修改通知的內容。
通知不在是簡單的文字了,可以加入視訊、圖片,自定義通知的展示等等。
iOS 10相對之前的通知來說更加好用易於管理,並且進行了大規模優化,對於開發者來說是一件好事。
iOS 10開始對於許可權問題進行了優化,申請許可權就比較簡單了(本地與遠端通知整合在一個方法中)。iOS 10 通知學習相關資料:
後面對UserNotifications單獨發表文章學習相關的知識
參考資料: