1. 程式人生 > >有關iOS11的適配問題

有關iOS11的適配問題

1. xcode9測試版執行自己的專案會發現專案沒有充滿螢幕,上下會有黑色區域的情況

  • 這是沒有設定對應的啟動圖,iPhone X對應畫素 1125 * 2436
    大家可以自己新增圖片或者準備一張尺寸:1125 * 2436的啟動圖片, 移動到LaunchImage的Finder目錄中, 並在LaunchImage中的Contents.json檔案中增加 (注意Json格式):
{
    "extent" : "full-screen",
    "idiom" : "iphone",
    "subtype" : "2436h",
    "filename" : "圖片名字.png",
    "minimum-system-version" : "11.0",
    "orientation" : "portrait",
    "scale" : "3x"
}

2. UITableview UICollectionView MJRefresh下拉重新整理錯亂

  • iOS11 UIScrollView and UITableView的新特性導致

如果有一些文字位於UI滾動檢視的內部,幷包含在導航控制器中,現在一般navigationContollers會傳入一個contentInset給其最頂層的viewController的scrollView,在iOS11中進行了一個很大的改變,不再通過scrollView的contentInset屬性了,而是新增了一個屬性:adjustedContentInset,通過下面兩種圖的對比,能夠表示adjustContentInset表示的區域:

adjustedContentInset.png
adjustedContentInset.png

contentInsetAdjustmentBehavior屬性有以下幾個列舉值:

/*
1. automatic 和scrollableAxes一樣,scrollView會自動計算和適應頂部和底部的內邊距並且在scrollView 不可滾動時,也會設定內邊距.
2. scrollableAxes 自動計算內邊距.
3. never不計算內邊距
4. always 根據safeAreaInsets 計算內邊距
*/
typedef NS_ENUM(NSInteger, UIScrollViewContentInsetAdjustmentBehavior) {  
    UIScrollViewContentInsetAdjustmentAutomatic, 
    UIScrollViewContentInsetAdjustmentScrollableAxes,
    UIScrollViewContentInsetAdjustmentNever,
    UIScrollViewContentInsetAdjustmentAlways,
}

@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior;
@property(nonatomic, readonly) UIEdgeInsets adjustedContentInset;

//adjustedContentInset值被改變的delegate
- (void)adjustedContentInsetDidChange; 
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView;

在我們專案中寫tableview的時候可以加下面的程式碼來解決此問題

if (@available(iOS 11.0, *)) {
    _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    _tableView.contentInset = UIEdgeInsetsMake(64, 0, 49, 0);//iPhoneX這裡是88
    _tableView.scrollIndicatorInsets = _tableView.contentInset;
}

在我自己專案中,我發現如果用的系統的導航欄的話距離上部可為0反而加上會有問題,列表會下移你設定的數值

_tableView.contentInset = UIEdgeInsetsMake(0, 0, 49, 0);
  // 實際專案中這樣也有出現列表的下面可能會被遮擋(針對部分專案,視情況而定) 針對我自己的專案我設定了向下導航欄的高度看似沒有問題了

3. tableView 頭部檢視和尾部檢視出現一塊留白問題

  • iOS11下tableview預設開啟了self-Sizing,Headers, footers, and cells都預設開啟Self-Sizing,所有estimated 高度預設值從iOS11之前的 0 改變為UITableViewAutomaticDimension
@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable

有兩種辦法去掉留白:

  • tableView的style:UITableViewStyleGrouped型別,預設tableView開頭和結尾是有間距的,不需要這個間距的話,可以通過實現heightForHeaderInSection方法(返回一個較小值:0.1)和viewForHeaderInSection(返回一個view)來去除頭部的留白,底部同理。

  • iOS 11上發生tableView頂部有留白,原因是程式碼中只實現了heightForHeaderInSection方法,而沒有實現viewForHeaderInSection方法。iOS 11之後應該是由於開啟了估算行高機制引起了bug。新增上viewForHeaderInSection方法後,問題就解決了。或者新增以下程式碼關閉估算行高,問題也得到解決。

self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;

4. 安全區域(Safe Area)

  • iOS 7 開始,在 UIViewController中引入的 topLayoutGuide 和 bottomLayoutGuide 在 iOS 11 中被廢棄了!取而代之的就是safeArea的概念,safeArea是描述你的檢視部分不被任何內容遮擋的方法。 它提供兩種方式:safeAreaInsets或safeAreaLayoutGuide來提供給你safeArea的參照值,即 insets 或者 layout guide。 safeArea區域如圖所示:
0.png
0.png
  • iOS11 為UIViewController和UIView增加了一個新的方法 - (void)viewSafeAreaInsetsDidChange;
    這個方法如名字一樣 在安全區域改變後會被呼叫, 我們可以在需要適配的UIViewController和UIView中重寫這個方法, 並且在這個方法中來根據safeAreaInsets屬性更新子檢視控制元件的佈局位置.

  • 這裡有一點要注意的是當UIViewController呼叫- (void)viewDidLoad時它的所有子檢視的safeAreaInsets屬性都等於UIEdgeInsetsZero, 也就是說在- (void)viewSafeAreaInsetsDidChange;方法呼叫前 是無法通過當前檢視控制器的子檢視獲取到safeAreaInsets的, 不過獲取當前window物件的safeAreaInsets屬性用來計算也是可以的, 但是不建議這麼做, 一個檢視控制器的子檢視的處理當然要以它所在的控制器為準.

  • (void)viewSafeAreaInsetsDidChange在UIViewController中第一次呼叫的時間是在- (void)viewWillAppear:(BOOL)animated呼叫之後, 在- (void)viewWillLayoutSubviews呼叫之前.
    當然如果你要改變一個UIViewController的safeAreaInsets值, 可以通過設定addtionalSafeAreaInsets屬性來實現, 例如你要自定義一些特殊的樣式時.
    如果你改變了一個UIViewController的safeAreaInsets屬性值, - (void)viewSafeAreaInsetsDidChange也會被呼叫.

5. 滑動刪除會崩
  • 在iOS8之後,蘋果官方增加了UITableVIew的右滑操作介面,即新增了一個代理方法(tableView: editActionsForRowAtIndexPath:)和一個類(UITableViewRowAction),代理方法返回的是一個數組,我們可以在這個代理方法中定義所需要的操作按鈕(刪除、置頂等),這些按鈕的類就是UITableViewRowAction。這個類只能定義按鈕的顯示文字、背景色、和按鈕事件。並且返回陣列的第一個元素在UITableViewCell的最右側顯示,最後一個元素在最左側顯示。從iOS 11開始有了一些改變,首先是可以給這些按鈕新增圖片了,然後是如果實現了以下兩個iOS 11新增的代理方法,將會取代(tableView: editActionsForRowAtIndexPath:)代理方法:
// Swipe actions
// These methods supersede -editActionsForRowAtIndexPath: if implemented
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath

這兩個代理方法返回的是UISwipeActionsConfiguration型別的物件,建立該物件及賦值可看下面的程式碼片段:

- ( UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
    //刪除
    UIContextualAction *deleteRowAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:@"delete" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
        [self.titleArr removeObjectAtIndex:indexPath.row];
        completionHandler (YES);
    }];
    deleteRowAction.image = [UIImage imageNamed:@"icon_del"];
    deleteRowAction.backgroundColor = [UIColor blueColor];

    UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteRowAction]];
    return config;
}
  • 建立UIContextualAction物件時,UIContextualActionStyle有兩種型別,如果是置頂、已讀等按鈕就使用UIContextualActionStyleNormal型別,delete操作按鈕可使用UIContextualActionStyleDestructive型別,當使用該型別時,如果是右滑操作,一直向右滑動某個cell,會直接執行刪除操作,不用再點選刪除按鈕,這也是一個好玩的更新。

  • 滑動操作這裡還有一個需要注意的是,當cell高度較小時,會只顯示image,不顯示title,當cell高度夠大時,會同時顯示image和title。我寫demo測試的時候,因為每個cell的高度都較小,所以只顯示image,然後我增加cell的高度後,就可以同時顯示image和title了。見下圖對比:

0 (1).png
0 (1).png

6. navigation bar

  • 導航欄新增了一種大標題樣式,預設設定是不開啟,所以不需要修改。

  • titleView支援autolayout,這要求titleView必須是能夠自撐開的或實現了- intrinsicContentSize

  • 解決辦法比較簡單,這個搜尋框對應的view實現- intrinsicContentSize方法

- (CGSize)intrinsicContentSize {
    return UILayoutFittingExpandedSize;
}

7. 導航欄返回按鈕

之前的程式碼通過下面的方式自定義返回按鈕

 UIImage *backButtonImage = [[UIImage imageNamed:@"icon_tabbar_back"]
    resizableImageWithCapInsets:UIEdgeInsetsMake(0, 18, 0, 0)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonImage
                                                  forState:UIControlStateNormal
                                               barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60)
                                                    forBarMetrics:UIBarMetricsDefault];

iOS 11 中setBackButtonTitlePositionAdjustment:UIOffsetMake沒法把按鈕移出navigation bar,
解決方法是設定navigationController的backIndicatorImage和backIndicatorTransitionMaskImage

UIImage *backButtonImage = [[UIImage imageNamed:@"icon_tabbar_back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
self.navigationBar.backIndicatorImage = backButtonImage;
self.navigationBar.backIndicatorTransitionMaskImage = backButtonImage;

8. 導航欄變化

1)導航欄高度變化

導航欄在iOS10之前都是預設的64p,但是,到了iOS10就不單單是64p了,可以看一下系統的資訊App,在iOS11添加了大標題,效果如下圖:


968977-410f670afe737036.png
968977-410f670afe737036.png

navigationBar的結構,看圖2、3、4:

圖2.png
圖2.png圖3.png
圖3.png圖4.png
圖4.png
  • 在上面三幅圖可以知道,在iOS11導航欄多了一個LargeTitleView,專門顯示大字標題用的,整個導航欄的高度達到了96p,這不包括狀態列的高度,也就是說,整個app頂部高度達到了116p,其中statusbar=20,title=44,largetitle=52,不過預設是64p;當然,iPhoneX的高度會更高點,如果不顯示大字標題,頂部的高度也達到了88,statusbar=44,title=44,如果顯示大字標題,則高度變成了140,statusbar=44,title=44,largetitle=52,也就是說,iPhoneX的劉海高度為24p,大字標題如下圖:
iphoneX.png
iphoneX.pngiphoneX之前的機型.png
iphoneX之前的機型.png
2.導航欄的圖層變化
  • iOS11之前導航欄的title是新增在UINavigationItemView上面,而navigationBarButton則直接新增在navigationBar上面;如果設定了titleView,則titleView也是直接新增在navigationBar上面,如圖5:
圖5.png
圖5.png
  • 在iOS11之後,蘋果添加了新的類來管理,navigationBar會新增在_UIButtonBarStackView上面,而_UIButtonBarStackView則新增在_UINavigationBarContentView上面;如果沒有給titleView賦值,則titleView會直接新增在_UINavigationBarContentView上面,如果賦值給了titleView,則會新生成_UITAMICAdaptorView,把titleView新增在這個類上面,這個類會新增在_UINavigationBarContentView上面,如下圖6、7:
圖6.png
圖6.png圖7.png
圖7.png
3) 導航欄的邊距變化
  • 在iOS11對導航欄裡面的item的邊距也做了調整,titleView調整最大的寬頻,邊距在iPhone6p上是20p,在iPhone6p以下是16p;在iOS11以下,這個邊距分別是12p和8p;如果設定了左右navigationBarButton,則在iOS11裡,navigationBarButton的邊距是20p和16p;在iOS11以下,也是20p和16p;如果同時設定了titleView和navigationBarButton,則在iOS11以下,它們之間的間距是6p,在iOS11則無間距,如下圖8、9:
圖8.png
圖8.png圖9.png
圖9.png
4) App需要實現導航欄左右按鈕邊距為0
  • 在iOS11之前,可以設定一個width為負的navigationBarButton,將按鈕擠到邊緣,變相實現0邊距的導航欄按鈕,但是,這招在iOS11失效了,原因在於_UIButtonBarStackView,這個iOS9之後出來的,用來相對佈局的元件,限制了子view的佈局。那怎麼搞呢?
  • 設定titleView,然後將button新增在titleView上面,根據不同的邊距做偏移。
  • 這個做法完全可以做到0邊距,但是,問題來了,就是點選區域的問題。因為左右navigationBarButton的點選區域是超出父view的,所以,點選不到。這好辦,重寫titleView的hitTest方法就好。嘿嘿嘿,問題沒有那麼簡單。之前在iOS11的圖層結構就解釋過,titleView會被新增在_UITAMICAdaptorView上面,而重點是,這個view也有邊距,所以,單單重寫titleView的hitTest方法還不夠,那怎麼解決呢?我的辦法就是寫一個view的類別,hook所有view的hitTest方法,在裡面判斷是否是iOS11以上,是否是_UITAMICAdaptorView類,如果都滿足條件,則可以搞事了。
5) UIBarItem

在UINavigationBar中新增了一個BOOL屬性prefersLargeTitles,將該屬性設定為ture,navigation bar就會在整個APP中顯示大標題,如果想要在控制不同頁面大標題的顯示,可以通過設定當前頁面的navigationItem的largeTitleDisplayMode屬性;

UINavigationController和滾動互動

滾動的時候,以下互動操作都是由UINavigationController負責調動的:

1.UIsearchController搜尋框效果更新

2.大標題效果的控制

3.Rubber banding效果 //當你開始往下拉,大標題會變大來回應那個滾輪

所以,如果你使用navigation bar,組裝push和pop體驗,你不會得到searchController的整合、大標題的控制更新和Rubber banding效果,因為這些都是由UINavigationController控制的。

9. iPhoneX底部tabbar的高度改變

iPhoneX不止多了劉海,底部還有一個半形的矩形,使得tabbar多出來了34p的高度,不過不管導航欄和tabbar一般系統都會自動適配safeArea。


iphoneX tabbar.png
iphoneX tabbar.png

10. Table Views:separatorInset 擴充套件

  • iOS 7 引入separatorInset屬性,用以設定 cell 的分割線邊距,在 iOS 11 中對其進行了擴充套件。可以通過新增的UITableViewSeparatorInsetReference列舉型別的separatorInsetReference屬性來設定separatorInset屬性的參照值。
typedef NS_ENUM(NSInteger, UITableViewSeparatorInsetReference) {  
UITableViewSeparatorInsetFromCellEdges,   //預設值,表示separatorInset是從cell的邊緣的偏移量
UITableViewSeparatorInsetFromAutomaticInsets  //表示separatorInset屬性值是從一個insets的偏移量
}

11. 管理margins 和 insets

layout margins
基於約束的Auto Layout,使我們搭建能夠動態響應內部和外部變化的使用者介面。Auto Layout為每一個view都定義了margin。margin指的是控制元件顯示內容部分的邊緣和控制元件邊緣的距離。可以用layoutMargins
或者layoutMarginsGuide屬性獲得view的margin,margin是檢視內部的一部分。layoutMargins允許獲取或者設定UIEdgeInsets結構的margin。layoutMarginsGuide則獲取到只讀的UILayoutGuide物件。
在iOS11新增了一個屬性:directional layout margins,該屬性是NSDirectionalEdgeInsets結構體型別的屬性:

typedef struct NSDirectionalEdgeInsets { CGFloat top, leading, bottom, trailing;} NSDirectionalEdgeInsets API_AVAILABLE(ios(11.0),tvos(11.0),watchos(4.0));

layoutMargins是UIEdgeInsets結構體型別的屬性:

typedef struct UIEdgeInsets { CGFloat top, left, bottom, right;} UIEdgeInsets;

從上面兩種結構體的對比可以看出,NSDirectionalEdgeInsets屬性用leading 和 trailing 取代了之前的 left 和 right。
directional layout margins屬性的說明如下:

directionalLayoutMargins.leading is used on the left when the user interface direction is LTR and on the right for RTL. Vice versa for directionalLayoutMargins.trailing.

例子:當你設定了trailing = 30;當在一個right to left 語言下trailing的值會被設定在view的左邊,可以通過layoutMargin的left屬性讀出該值。如下圖所示:




還有其他一些更新。自從引入layout margins,當將一個view新增到viewController時,viewController
會修復view的的layoutMargins
為UIKit定義的一個值,這些調整對外是封閉的。從iOS11開始,這些不再是一個固定的值,它們實際是最小值,你可以改變view的layoutMargins為任意一個更大的值。而且,viewController新增了一個屬性:viewRespectsSystemMinimumLayoutMargins,如果你設定該屬性為"false",你就可以改變你的layoutMargins為任意你想設定的值,包括0,導航欄TitileView的寬度設定
在導航titleView使用SearchBar寬度適配問題?如果您在Navigation上的titleView上新增searchBar,iOS11情況下可能有問題,如下圖所示:

12 .Xcode9下相簿等訪問許可權問題

之前專案中相機功能一直使用系統自帶的PickerView,說實話不甚美觀,自己空閒之餘一直著手開發自定義相機(EVNCamera:給個StarO(∩_∩)O~)。在Xcode9的首個Beta版本中開發相機功能時發現,原有專案竟然crash,後來發現iOS11下,蘋果對相簿的許可權key做了調整,原來的 NSPhotoLibraryUsageDescription ,在iOS11之後,改成了NSPhotoLibraryAddUsageDescription。詳見:Cocoa Keys

13.近場通訊NFC許可權