一呢撞、safeArea
-
automaticallyAdjustsScrollViewInsets
tocontentInsetAdjustmentBehavior
在iOS 11中,蘋(píng)果廢棄了UIViewController
的automaticallyAdjustsScrollViewInsets
屬性套蒂,改用UIScrollView
的contentInsetAdjustmentBehavior
屬性來(lái)替換它。
// OC
@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets API_DEPRECATED_WITH_REPLACEMENT("Use UIScrollView's contentInsetAdjustmentBehavior instead", ios(7.0,11.0),tvos(7.0,11.0)); // Defaults to YES
//swift
@available(iOS, introduced: 7.0, deprecated: 11.0)
open var automaticallyAdjustsScrollViewInsets: Bool // Defaults to YES
如果你的工程中沒(méi)有添加 iPhone X
對(duì)應(yīng)的啟動(dòng)圖,你會(huì)發(fā)現(xiàn)頂部和底部的一部分都是黑色的,那部分就是安全域。
想要對(duì) iPhone X
做屏幕適配芯杀,第一步先要添加啟動(dòng)圖。
二雅潭、UITableView
自動(dòng)計(jì)算高度
在iOS 11中,UITableView
的 estimatedRowHeight
却特、
estimatedSectionHeaderHeight
扶供、
estimatedSectionFooterHeight
默認(rèn)都是開(kāi)啟的。真是令人驚喜裂明。
@property (nonatomic) CGFloat rowHeight; // default is UITableViewAutomaticDimension
@property (nonatomic) CGFloat sectionHeaderHeight; // default is UITableViewAutomaticDimension
@property (nonatomic) CGFloat sectionFooterHeight; // default is UITableViewAutomaticDimension
@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable
@property (nonatomic) CGFloat estimatedSectionHeaderHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable
@property (nonatomic) CGFloat estimatedSectionFooterHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable
踩坑1
在我們的app中椿浓,首頁(yè)的 UITableView
列表是手動(dòng)計(jì)算的行高,突然出現(xiàn)了以往只有開(kāi)啟自動(dòng)計(jì)算行高才會(huì)出現(xiàn)的滾動(dòng)條跳動(dòng)的現(xiàn)象,給 TableView
的 contentSize
加了observer
扳碍,果然一直在變提岔。然后在這里找到了原因。
踩坑2 (更新)
在使用 MJRefersh
的時(shí)候笋敞,本身代碼存在一個(gè) bug
碱蒙,原來(lái)并沒(méi)有發(fā)現(xiàn),iOS 11 幫我發(fā)現(xiàn)了這個(gè)問(wèn)題夯巷。
// tableView 設(shè)置
let footer = MJRefreshAutoFooter(refreshingBlock: { [weak self] in
if let strongSelf = self {
if let block = strongSelf.footerBlock {
block()
}
}
})
footer?.triggerAutomaticallyRefreshPercent = -20
tableView.mj_footer = footer
// 請(qǐng)求結(jié)束后處理(錯(cuò)誤)
models.append(requestModels)
if models.count < pageSize { // 這里是個(gè)bug
tableView.mj_footer.endRefreshingWithNoMoreData()
} else {
tableView.mj_footer.state = .idle
}
tableView.reloadData()
// 請(qǐng)求結(jié)束后處理(正確)
if requestModels.count < pageSize { // 修復(fù)bug
tableView.mj_footer.endRefreshingWithNoMoreData()
} else {
tableView.mj_footer.state = .idle
}
models.append(requestModels)
tableView.reloadData()
上面代碼中赛惩,在 iOS 11
之前也存在隱含的問(wèn)題,就是 tableView footer
一直不能被標(biāo)記為 無(wú)更多數(shù)據(jù)
的狀態(tài)趁餐,即使沒(méi)有數(shù)據(jù)了喷兼,用戶(hù)下拉依然后發(fā)起請(qǐng)求。
在 iOS 11
中后雷,由于估算行高季惯,互動(dòng)到底部時(shí),依然會(huì)觸發(fā) tableView
的 contentSize
和 contentOffset
變化臀突,同時(shí)會(huì)觸發(fā) refreshingBlock
, 和 tableView
的 reloadData
勉抓,導(dǎo)致了最后一頁(yè)數(shù)據(jù)陷入了循環(huán)請(qǐng)求。
引申問(wèn)題
如果存在使用監(jiān)聽(tīng) tableView
的 contentSize
和 contentOffset
變化來(lái)觸發(fā)的一些事件惧辈,最好把 tableView
的自動(dòng)計(jì)算行高關(guān)掉琳状,以免出現(xiàn)問(wèn)題。
三盒齿、iPhone X(嚴(yán)重)
1. 狀態(tài)欄獲取網(wǎng)絡(luò)狀態(tài)
眾所周知念逞,iPhone X多了個(gè)“美美的”劉海,它的狀態(tài)欄發(fā)生了變化边翁。這也導(dǎo)致了原來(lái)從狀態(tài)欄視圖獲取網(wǎng)絡(luò)狀態(tài)的API出了問(wèn)題翎承,會(huì)導(dǎo)致閃退。
// 獲取當(dāng)前app狀態(tài)欄子視圖
UIApplication *app = [UIApplication sharedApplication];
NSArray *children = [[[app valueForKeyPath:@"statusBar"] valueForKeyPath:@"foregroundView"] subviews];
int type = 0;
for (id child in children) {
if ([child isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {
type = [[child valueForKeyPath:@"dataNetworkType"] intValue];
}
}
switch (type) {
case 1:
return @"2G";
case 2:
return @"3G";
case 3:
return @"4G";
case 5:
return @"WIFI";
default:
return @"Unknow";
break;
}
不過(guò)你依然可以用AFNetworking
類(lèi)似的方式符匾,獲取網(wǎng)絡(luò)狀態(tài):
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
switch (self.networkReachabilityAssociation) {
case AFNetworkReachabilityForName:
break;
case AFNetworkReachabilityForAddress:
case AFNetworkReachabilityForAddressPair:
default: {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
SCNetworkReachabilityGetFlags(self.networkReachability, &flags);
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
dispatch_async(dispatch_get_main_queue(), ^{
callback(status);
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:@{ AFNetworkingReachabilityNotificationStatusItem: @(status) }];
});
});
}
break;
}
static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
if (isNetworkReachable == NO) {
status = AFNetworkReachabilityStatusNotReachable;
}
#if TARGET_OS_IPHONE
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
status = AFNetworkReachabilityStatusReachableViaWWAN;
}
#endif
else {
status = AFNetworkReachabilityStatusReachableViaWiFi;
}
return status;
}
2.狀態(tài)欄
- 狀態(tài)欄高度由原來(lái)的20叨咖,變?yōu)?4了,以前使用常量的就被坑了啊胶。
- 在修改我們的宏過(guò)程中遇到了另外一個(gè)坑甸各,特此提醒:
// 原有
#define MPStatusBarHeight (20)
// 新的
#define MPStatusBarHeight (UIApplication.sharedApplication.statusBarFrame.size.height)
/* 坑在此處 */
// application 的 statusBarFrame 在隱藏狀態(tài)欄的時(shí)候是 CGRectZero
@property(nonatomic,readonly) CGRect statusBarFrame __TVOS_PROHIBITED; // returns CGRectZero if the status bar is hidden
3.友盟分享
友盟分享 SDK 6.4.6 版本提供的界面沒(méi)有適配 iPhone X
需要更新 SDK
到最新的 6.8.0 版本,很遺憾的是焰坪,目前最新的友盟所有產(chǎn)品都沒(méi)有提供 cocoapods
版本趣倾,需要手動(dòng)集成。
在集成過(guò)程中遇到一個(gè)坑:
由于我們工程同時(shí)使用 pod
引用了友盟分享和友盟統(tǒng)計(jì)某饰,本來(lái)只想升級(jí)友盟分享儒恋,于是直接跳到集成友盟分享的文檔部分善绎,執(zhí)行了下面的操作:
1、移除pod對(duì) UMShare的引用诫尽,執(zhí)行 pod install
2禀酱、添加UMShareSDK到工程
3、修改文件引用錯(cuò)誤
4牧嫉、清除DerivedData
5剂跟、在模擬器build
報(bào)錯(cuò):
common
基礎(chǔ)庫(kù)浩聋,回過(guò)頭來(lái)看文檔,發(fā)現(xiàn)集成友盟任意庫(kù)都需要加入 common
基礎(chǔ)庫(kù)臊恋。但是一旦加入
common
基礎(chǔ)庫(kù)衣洁,又會(huì)和使用 pod
引用的友盟統(tǒng)計(jì)沖突,無(wú)奈抖仅,只能統(tǒng)計(jì)庫(kù)和分享庫(kù)都換成手動(dòng)引用坊夫。2018.01.10更新:
友盟6.8.1版本已提供 cocoapods 集成方式,直接更新即可撤卢。
四环凿、UIToolBar
在 UIToolBar
中兩側(cè)添加了 UIBarButtonItem
,在 iOS 11
之前會(huì)布局在邊放吩,在 iOS 11
中兩個(gè)item是緊挨著的智听,需要在中間添加 UIBarButtonSystemItemFlexibleSpace
類(lèi)型的 item
才可以。
五渡紫、ALAssetsLibrary
保存圖片閃退
在 iOS 11 之前到推,你可以直接使用下面代碼來(lái)保存圖片到相冊(cè):
#import <AssetsLibrary/AssetsLibrary.h>
// ALAssetsLibrary
[[[ALAssetsLibrary alloc] init] writeImageDataToSavedPhotosAlbum:imageData metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) {
[self saveResultWithResult:(error == nil)];
}];
在 iOS 11中則會(huì)直接崩潰,保存圖片的新姿勢(shì):
- 解決方案1:
在 infoPlist 文件中追加相冊(cè)寫(xiě)入權(quán)限的提示
參數(shù) | key | Xcode name | 版本 | 說(shuō)明 |
---|---|---|---|---|
NSPhotoLibraryAddUsageDescription | "Privacy - Photo Library Additions Usage Description" | Specifies the reason for your app to get write-only access to the user’s photo library. See NSPhotoLibraryAddUsageDescription for details. | iOS 11 and later | 本參數(shù)iOS 11必須追加 |
NSPhotoLibraryUsageDescription | “Privacy - Photo Library Usage Description” | Specifies the reason for your app to access the user’s photo library. See NSPhotoLibraryUsageDescription for details. | iOS 6.0 and later | 本參數(shù)iOS 10以后必須追加 |
在infoPlist中直接添加(xcode 中 open as Source Code):
<key>NSPhotoLibraryUsageDescription</key>
<string>此 App 需要您的同意才能讀取媒體資料庫(kù)</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>此 App 需要您的同意才能寫(xiě)入媒體資料庫(kù)</string>
-
解決方案2:
如果沒(méi)有按方案1添加寫(xiě)入權(quán)限提示惕澎,ALAssetsLibrary
寫(xiě)入直接崩潰莉测,而PHPhotoLibrary
保存圖片會(huì)直接觸發(fā)向用戶(hù)請(qǐng)求相冊(cè)寫(xiě)入權(quán)限的 Alert 提示。
#import <Photos/Photos.h>
// PHPhotoLibrary
- (void)saveImageByPHPhotoLibrary:(UIImage *)image {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetCreationRequest creationRequestForAssetFromImage:image];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self saveResultWithResult:(success && error == nil)];
});
}];
}
更新
由于在應(yīng)用中使用了 WKWebView
, WKWebView
在沒(méi)有處理的情況下唧喉,長(zhǎng)按圖片會(huì)彈出保存圖片的選項(xiàng)捣卤,選擇保存圖片同樣會(huì)崩潰。所以該方案不能處理好所有入口八孝,推薦使用方案1董朝。
- 解決方案3:
更為合理的做法是,在向相冊(cè)寫(xiě)入圖片之前干跛,首先請(qǐng)求相冊(cè)權(quán)限子姜,參考iOS相冊(cè)、相機(jī)驯鳖、通訊錄權(quán)限獲取
六闲询、鍵盤(pán)鑰匙串 (password auto fill)
1、問(wèn)題
在iOS 11系統(tǒng)中浅辙,你會(huì)發(fā)現(xiàn)你原本的一些輸入框喚起系統(tǒng)鍵盤(pán)后出現(xiàn)如下的狀況:
鍵盤(pán)右上角的??扭弧,是iOS 11的新特性 Password Auto Fill
,假如你在代碼中設(shè)置 UITextField
的 contentType 為 username
和 password
類(lèi)型记舆,就會(huì)出現(xiàn)上面的圖標(biāo)鸽捻。
usernameTextField.textContentType = UITextContentType.username
passwordTextField.textContentType = UITextContentType.password
而有些情況,在 XIB
文件中泽腮,如果你沒(méi)有指定特定類(lèi)型御蒲,鍵盤(pán)也會(huì)出現(xiàn)??:
對(duì)應(yīng)情況你只需要將 contentType
設(shè)置為其它類(lèi)型即可:
2、關(guān)于 Password Auto Fill
Password Auto Fill
的適配可以參考iOS 11 ---- Password Auto Fill
(PS: 感覺(jué)機(jī)制有點(diǎn)類(lèi)似UniversalLink)
七诊赊、react-native
可以參考下面代碼厚满,來(lái)適配狀態(tài)欄和底部安全域:
import { Dimensions, Platform } from 'react-native'
const { width, height } = Dimensions.get('window')
export const isIphoneX = () => (
Platform.OS === 'ios' &&
!Platform.isPad &&
!Platform.isTVOS &&
(height === 812 || width === 812)
)
const getStatusBarHeight = () => {
if (Platform.OS === 'android') {
return 0
}
if (isIphoneX()) {
return 44
}
return 20
}
// const
export const IPHONEX_BOTTOM_HEIGHT = 34
export const STATUSBAR_HEIGHT = getStatusBarHeight()