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層面
1.Dark Mode - 審核強(qiáng)制要求適配黑夜模式。
2.Sign In with Apple - 提供第三方登錄
3.模態(tài)彈出默認(rèn)交互改變(必須)
4.UISegmentedControl 默認(rèn)樣式改變(必須)
5.h5的適配,內(nèi)嵌WebView,一些圖片路徑,文件路徑,不用寫(xiě)絕對(duì)路徑,直接寫(xiě)文件名字即可
二、代碼層面
1.私有方法 KVC 不允許使用:valueForKey、setValue:forKey:(必須)
2.推送的 deviceToken 獲取到的格式發(fā)生變化(必須)
3.UISearchBar 黑線處理導(dǎo)致崩潰
4.使用 UISearchDisplayController 導(dǎo)致崩潰
5.模態(tài)彈出默認(rèn)交互改變(必須)
6.MPMoviePlayerController 在iOS13被棄用
7.LaunchImage 被棄用(必須)
8.Xcode 11 創(chuàng)建的工程在低版本設(shè)備上運(yùn)行黑屏
9.使用 @available 導(dǎo)致舊版本 Xcode 編譯出錯(cuò)。(必須)
10.textfield.leftview(必須)
11.NSAttributedString優(yōu)化
12.TabBar紅點(diǎn)偏移
13.廢棄UIWebView(必須)
14.WKWebView 中測(cè)量頁(yè)面內(nèi)容高度的方式變更
15.使用MJExtension 中處理NSNull的不同(必須)
16.StatusBar 與之前版本不同(必須)
17.UIActivityIndicatorView(必須)
18.藍(lán)牙權(quán)限需要申請(qǐng)
19.CNCopyCurrentNetworkInfo
20.下面這種獲取網(wǎng)絡(luò)狀態(tài)用到KVC的方法會(huì)發(fā)生崩潰
21.MPRemoteCommandCenter 音頻后臺(tái)播放控制器
一、UI層面
注:必須適配的點(diǎn)以“(必須)”標(biāo)出,有的是看項(xiàng)目需求,大家可忽略
1.Dark Mode - 審核強(qiáng)制要求適配黑夜模式。
iOS 13 推出暗黑模式,UIKit 提供新的系統(tǒng)顏色和 api 來(lái)適配不同顏色模式,xcassets 對(duì)素材適配也做了調(diào)整,官方具體適配可見(jiàn): 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)用支持使用第三方登錄,那么就必須加上蘋(píng)果新推出的登錄方式:Introducing Sign In with Apple。目前蘋(píng)果只在 News and Updates 上提到正式發(fā)布時(shí)要求加上,具體發(fā)布時(shí)間還沒(méi)確定。
3.模態(tài)彈出默認(rèn)交互改變(必須)
在 iOS 13 中此枚舉值直接成為了模態(tài)彈出的默認(rèn)值,因此 presentViewController 方式打開(kāi)視圖是如下的視差效果,默認(rèn)是下滑返回。
iOS13下仍然可以做到全屏彈出,運(yùn)行代碼發(fā)現(xiàn)presentViewController和之前彈出的樣式不一樣
會(huì)出現(xiàn)這種情況是主要是因?yàn)槲覀冎皩?duì)UIViewController里面的一個(gè)屬性,即modalPresentationStyle(該屬性是控制器在模態(tài)視圖時(shí)將要使用的樣式)沒(méi)有設(shè)置需要的類型。在iOS13中modalPresentationStyle的默認(rèn)改為UIModalPresentationAutomatic,而在之前默認(rèn)是UIModalPresentationFullScreen。
ViewController *vc = [[ViewController alloc] init];
vc.title = @"presentVC";
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
nav.modalPresentationStyle = UIModalPresentationFullScreen;
[self.window.rootViewController presentViewController:nav animated:YES completion:nil];
注意:如果你某個(gè)控制器想使用卡片模式,需要注意你這個(gè)控制器底部是否有控件。卡片式的底部的控件容易被遮擋。
比方說(shuō)TZPhotoPickerController 這個(gè)常用的開(kāi)源相冊(cè)控件,當(dāng)選擇照片時(shí)底部的確定按鈕就被遮擋,無(wú)法選中.
4.UISegmentedControl 默認(rèn)樣式改變(必須)
默認(rèn)樣式變?yōu)榘椎缀谧郑绻O(shè)置修改過(guò)顏色的話,頁(yè)面需要修改。
原本設(shè)置選中顏色的 tintColor 已經(jīng)失效,新增了 selectedSegmentTintColor 屬性用以修改選中的顏色。
Web Content適配
if ( @available(iOS 13.0, *)) {
self.segmentedControl.selectedSegmentTintColor = [UIColor clearColor];
}else
{
self.segmentedControl.tintColor = [UIColor clearColor];
}
5.h5的適配,參考鏈接:
內(nèi)嵌WebView,一些圖片路徑,文件路徑,不用寫(xiě)絕對(duì)路徑,直接寫(xiě)文件名字即可
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!沒(méi)有找到更好的辦法
return [self valueForKey:@"searchField"];
}
@end
大家可以集思廣益
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 之后,蘋(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.'
另外說(shuō)一下,在 iOS 13 中終于可以獲取直接獲取搜索的文本框:
_searchBar.searchTextField.text = @“search";
5.模態(tài)彈出默認(rèn)交互改變(必須)
在iOS13中運(yùn)行代碼發(fā)現(xiàn)presentViewController和之前彈出的樣式不一樣。是因?yàn)橹皩?duì)UIViewController里面的一個(gè)屬性,即modalPresentationStyle(該屬性是控制器在模態(tài)視圖時(shí)將要使用的樣式)沒(méi)有設(shè)置需要的類型。在iOS13中modalPresentationStyle的默認(rèn)改為UIModalPresentationAutomatic,而在之前默認(rèn)是UIModalPresentationFullScreen。
注意:如果你某個(gè)控制器想使用卡片模式,需要注意你這個(gè)控制器底部是否有控件。卡片式的底部的控件容易被遮擋。比方說(shuō)TZPhotoPickerController 這個(gè)常用的開(kāi)源相冊(cè)控件。當(dāng)選擇照片時(shí)底部的確定按鈕就被遮擋。無(wú)法選中
如果需要做成全屏顯示的界面,需要手動(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;
不太多的話還是建議老老實(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 在iOS13被棄用
在 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。
7.LaunchImage 被棄用(必須)
iOS 8 之前我們是在LaunchImage 來(lái)設(shè)置啟動(dòng)圖,但是隨著蘋(píng)果設(shè)備尺寸越來(lái)越多,我們需要在對(duì)應(yīng)的 aseets 里面放入所有尺寸的啟動(dòng)圖,這是非常繁瑣的一個(gè)步驟。因此在 iOS 8 蘋(píng)果引入了 LaunchScreen.storyboard,支持界面布局用的 AutoLayout + SizeClass ,可以很方便適配各種屏幕。
需要注意的是,蘋(píng)果在 Modernizing Your UI for iOS 13 section 中提到,從2020年4月開(kāi)始,所有支持 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 中管理。但是舊版本根本沒(méi)有 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 方法沒(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
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)確, 那就沒(méi)辦法了, 我只能改寫(xiě)這個(gè)方法了,這個(gè)組件沒(méi)有更新的情況下,寫(xiě)了一個(gè)方法swizzling掉把當(dāng)遇到 NSNull時(shí),直接轉(zhuǎn)為nil了。
有人問(wèn)這個(gè)方法怎么實(shí)現(xiàn),其實(shí)目前我們項(xiàng)目在ios13下面還沒(méi)遇到這種情況,發(fā)一個(gè)之前項(xiàng)目處理null的方法,在項(xiàng)目里面寫(xiě)一個(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 與之前版本不同(必須)
目前狀態(tài)欄也增加了一種模式,由之前的兩種,變成了三種, 其中default由之前的黑色內(nèi)容,變成了會(huì)根據(jù)系統(tǒng)模式,自動(dòng)選擇當(dāng)前展示lightContent還是darkContent。
public enum UIStatusBarStyle : Int {
case `default` // Automatically chooses light or dark content based on the user interface style
@available(iOS 7.0, *)
case lightContent // Light content, for use on dark backgrounds
@available(iOS 13.0, *)
case darkContent // Dark content, for use on light backgrounds
}
我們?cè)谑褂玫臅r(shí)候,就可以重寫(xiě)preferredStatusBarStyle的get方法:
override var preferredStatusBarStyle: UIStatusBarStyle{
get{
return .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)打開(kāi)權(quán)限
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
NSLog(@"User has explicitly denied authorization for this application, or location services are disabled in Settings.");
//使用下面接口可以打開(kāi)當(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){
//彈框提示用戶是否開(kāi)啟位置權(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 以后只有開(kāi)啟了 Access WiFi Information capability,才能獲取到 SSID 和 BSSID wi-fi or wlan 相關(guān)使用變更
最近收到了蘋(píng)果的郵件,說(shuō)獲取WiFi SSID的接口CNCopyCurrentNetworkInfo 不再返回SSID的值。不仔細(xì)看還真會(huì)被嚇一跳,對(duì)物聯(lián)網(wǎng)的相關(guān)APP簡(jiǎn)直是炸彈。仔細(xì)看郵件還好說(shuō)明了可以先獲取用戶位置權(quán)限才能返回SSID。
注意:目本身已經(jīng)打開(kāi)位置權(quán)限,則可以直接獲取
20.下面這種獲取網(wǎng)絡(luò)狀態(tài)用到KVC的方法會(huì)發(fā)生崩潰
UIApplication *app = [UIApplication sharedApplication];
NSArray *children = nil;
// 不能用 [[self deviceVersion] isEqualToString:@"iPhone X"] 來(lái)判斷,因?yàn)槟M器不會(huì)返回 iPhone X
id statusBar = [app valueForKeyPath:@"statusBar"];
if ([statusBar isKindOfClass:NSClassFromString(@"UIStatusBar_Modern")]) {
children = [[[statusBar valueForKey:@"statusBar"] valueForKey:@"foregroundView"] subviews];
} else {
children = [[statusBar valueForKey:@"foregroundView"] subviews];
}
NSString *state = [[NSString alloc] init];
int netType = 0;
for (id child in children) {
if ([child isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {
//獲取到狀態(tài)欄
netType = [[child valueForKeyPath:@"dataNetworkType"] intValue];
switch (netType) {
case 0:
state = @"none";
break;
case 1:
state = @"2G";
break;
case 2:
state = @"3G";
break;
case 3:
state = @"4G";
break;
case 5:
state = @"WIFI";
break;
default:
break;
}
}
}
21.MPRemoteCommandCenter 音頻后臺(tái)播放控制器
- (void)addTarget:(id)target action:(SEL)action; 在iOS 13中selector 一定要返回MPRemoteCommandHandlerStatus,要不然會(huì)閃退。
// Target-action style for adding handlers to commands.
// Actions receive an MPRemoteCommandEvent as the first parameter.
// Targets are not retained by addTarget:action:, and should be removed from the
// command when the target is deallocated.
//
// Your selector should return a MPRemoteCommandHandlerStatus value when
// possible. This allows the system to respond appropriately to commands that
// may not have been able to be executed in accordance with the application's
// current state.
- (void)addTarget:(id)target action:(SEL)action;
- (void)removeTarget:(id)target action:(nullable SEL)action;
- (void)removeTarget:(nullable id)target;