亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > iOS > 正文

iOS 11 safeArea詳解及iphoneX 適配

2019-10-21 18:41:41
字體:
來源:轉載
供稿:網友

最近看了許多iPhone X適配的文章,發現很少有介紹safeArea的,就來隨便寫寫

現在對于iPhone X的適配,有一種常見的做法是給導航欄或tabbar增加一個固定的距離,比如頂部增加44pt,底部增加34pt。這種寫死距離的做法乍看上去挺簡單,其實并不好,理由如下

  1. 不適合多機型的適配,如果以后出了一種帶劉海的iPad,需要預留出來的距離就未必是現在寫死的距離
  2. 不適合需要支持橫豎屏的app,橫屏頂部不需要增加距離,反而是左右各有44pt,底部的距離也和豎屏不同
  3. 不夠動態。還是舉個例子,假如有電話打進來了,導航欄應該會下移,這時候view可能還是會被擋住

這里我想探討一下如何使用safeAreaLayoutGuide和safeAreaInsets,以一種動態的方式,一勞永逸地解決iPhone X甚至后續所有機型的適配問題。

safeAreaLayoutGuide

首先我們看看什么是safeAreaLayoutGuide

iOS11,safeArea

看起來復雜,其實很簡單,我歸納一下有幾點:

  1. 它是UIView的一個只讀屬性,意味著所有UIView對象都有并且是系統幫我們創建好的
  2. 它繼承UILayoutGuide,有layoutFrame意味著它能代表一塊區域
  3. 它代表的區域避開了諸如導航欄、tabbar或者其他有可能擋住你這個UIView對象顯示的所有父view,意味著你的view對象只要相對另一個view的safeLayoutGuide做布局就不用擔心她被奇奇怪怪的東西擋住
  4. 對于控制器的view的safeAreaLayoutGuide,他的區域同樣避開了statusbar或其他有可能擋住view顯示的東西,我們甚至可以用控制器的additionalSafeAreaInsets屬性,來額外指定inset
  5. 如果view完全在父view的安全區域內,或者view不在視圖層級或屏幕上,那么view的safeAreaLayoutGuide區域其實和view自身是一樣大的

safeAreaLayoutGuide是一個相對抽象的概念,為了便于理解,我們可以把safeAreaLayoutGuide看成是一個“view”,這個“view”系統自動幫我們調整它的bounds,讓它不會被各種奇奇怪怪的東西擋住,包括iPhone X的劉海區域和底部的一道杠區域,可以認為在這個“view”上一定能完整顯示所有內容。

以下綠色部分就是當前控制器view的safeAreaLayoutGuide區域

iOS11,safeArea

iphone X豎屏safeAreaLayoutGuide的bounds.png

iOS11,safeArea

iPhone X橫屏safeAreaLayoutGuide的bounds.png

截圖來自https://developer.apple.com/videos/play/fall2017/801/

不過需要銘記的一點是這個“view”并不會顯示在我們的視圖層級上。
UILayoutGuides will not show up in the view hierarchy, but may be used as items in an NSLayoutConstraint and represent a rectangle in the layout engine.

在我看來,他最大的作用是作為參照物,讓view可以相對某個view的safeAreaLayoutGuide做布局,從而保證view能正常、安全地顯示(相對的那個view不一定要是父view)

在一種常見的使用場景里,以前我的某個view是相對于控制器的view做布局,現在是相對控制器view的safeAreaLayoutGuide做布局了

以前是這樣寫
[NSLayoutConstraint constraintWithItem:someView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.vc.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];

現在是這樣
[NSLayoutConstraint constraintWithItem:someView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.vc.view.safeAreaLayoutGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];

適配前后的效果

iOS11,safeArea

適配前.png

iOS11,safeArea

改成相對于view的safeAreaLayoutGuide后-豎屏.png

iOS11,safeArea

改成相對于view的safeAreaLayoutGuide后-橫屏.png

可以看到,相同的布局下,橫屏在沒有statusbar時,距離頂部是0,左邊是44,如果有statusbar,距離頂部就是20。反正不管怎么弄,只要我們相對safeAreaLayoutGuide做布局,我們的view就能夠安全完整地顯示出來

那么非iOS11怎么辦?

非iOS11 還是只能對view做布局,就要寫兩套布局代碼,稍后會介紹

這樣是不是就足夠應對所有情況了呢?

并不是

我們自定義的view有一邊需要緊挨著屏幕邊緣,比如我項目里是自定義的導航欄,它的頂部是挨著屏幕頂部的,那么導航欄就不能相對view的safeAreaLayoutGuide布局,否則頂部會空出來一截子

frame布局

這時就輪到safeAreaInsets來發揮作用啦

safeAreaInsets

先看一下safeAreaInsets的官方解釋

iOS11,safeArea

有沒有覺得和safeAreaLayoutGuide很像?safeAreaLayoutGuide可能就是根據safeAreaInsets來調整自己的bounds的

iPhone X豎屏時占滿整個屏幕的控制器的view的safeAreaInsets是(44,0,34,0),橫屏是(0,44,21,44),inset后的區域正好是safeAreaLayoutGuide區域

既然如此,對于自定義的頂部導航欄來說,我們可以給導航欄的高度加上一個vc.view.safeAreaInsets.top,讓他變高一點就可以了,這樣在X上,豎屏時top = 44, 橫屏時top = 0,導航欄的高度能響應改變

需要注意的是,無論safeAreaLayoutGuide還是safeAreaInsets都是iOS11才能使用的。
對于safeAreaInsets,我們可以把版本判斷寫在一個函數里

我們可以這樣寫

static inline UIEdgeInsets sgm_safeAreaInset(UIView *view) { if (@available(iOS 11.0, *)) {  return view.safeAreaInsets; } return UIEdgeInsetsZero;}
UIEdgeInsets safeAreaInsets = sgm_safeAreaInset(self.view);CGFloat height = kDefaultTopViewHeight; // 導航欄原本的高度,通常是44.0height += safeAreaInsets.top > 0 ? safeAreaInsets.top : 20.0; // 20.0是statusbar的高度

問題又來了,這段代碼放在什么地方合適呢?前面官方文檔提到過,如果view不在屏幕上或顯示層級里,view的safeAreaInsets = UIEdgeInsetsZero,所以我們需要明確知道safeAreaInsets改變的時機

實際上系統已經提供了回調

對于UIViewController

 

復制代碼 代碼如下:

-(void)viewSafeAreaInsetsDidChange NS_REQUIRES_SUPER API_AVAILABLE(ios(11.0), tvos(11.0));

 

對于UIView

-(void)safeAreaInsetsDidChange API_AVAILABLE(ios(11.0),tvos(11.0));

這里主要探討controller的回調,view的回調是類似的。只要controller的view的safeAreaInsets改變,系統就會調用viewSafeAreaInsetsDidChange。自然而然,我們會想把以上代碼放在這里,然而這里有個大坑,你會發現,當這個控制器以動畫的方式push進來時,導航欄的高度也會動畫地變高,產生了不必要的多余動畫,這種體驗很糟糕

那么究竟應該放在哪里?我們很有必要看一下新的viewController調用時序

以下是從“rootVC” push 到 “pushVC”控制臺輸出的調用時序以及對應控制器的view的safeAreaInsets

2017-10-04 16:59:59.594811+0800 XXX[15662:803767] Begin pushViewController to [<_TtCC8XXXTests27ContainerViewControllerTest20MockUIViewController: 0x7f9c07b643b0>]viewDidLoad()---Optional("pushVC")---UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)willMove(toParentViewController:)---Optional("pushVC")---UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)viewWillDisappear---Optional("rootVC")---UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)viewWillAppear---Optional("pushVC")---UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)viewSafeAreaInsetsDidChange()---Optional("pushVC")---UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)viewWillLayoutSubviews()---Optional("pushVC")---UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)viewDidLayoutSubviews()---Optional("pushVC")---UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)viewWillLayoutSubviews()---Optional("pushVC")---UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)viewDidLayoutSubviews()---Optional("pushVC")---UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)viewDidAppear---Optional("pushVC")---UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)viewDidDisappear---Optional("rootVC")---UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)didMove(toParentViewController:)---Optional("pushVC")---UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)2017-10-04 16:59:59.604563+0800 XXX[15662:803767] Did PushViewController [<_TtCC8XXXTests27ContainerViewControllerTest20MockUIViewController: 0x7f9c0790d170>]->[<_TtCC8XXXTests27ContainerViewControllerTest20MockUIViewController: 0x7f9c07b643b0>] time = [0.009772]

可以看到,viewSafeAreaInsetsDidChange調用時機很早,在viewWillAppear后,這是為什么出現多余動畫的原因。并且“pushVC”的safeAreaInsets直到viewSafeAreaInsetsDidChange調用前,都是UIEdgeInsetsZero,之后才是正確的UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)

并且viewSafeAreaInsetsDidChange后面會調用兩次viewDidLayoutSubviews,所以我們應該把改變高度或布局的代碼都寫在viewDidLayoutSubviews里,這樣就不會有多余的動畫效果了。需要注意viewDidLayoutSubviews可能會由別的操作頻繁觸發,所以如果調整safeArea布局的代碼比較耗時,可以考慮加上一個狀態標記,只在didChange后執行一次布局調整

最后的代碼應該長這樣

- (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; UIEdgeInsets safeAreaInsets = sgm_safeAreaInset(self.view); CGFloat height = 44.0; // 導航欄原本的高度,通常是44.0 height += safeAreaInsets.top > 0 ? safeAreaInsets.top : 20.0; // 20.0是statusbar的高度,這里假設statusbar不消失 if (_navigationbar && _navigationbar.height != height) {  _navigationbar.height = height; }

適配前后的效果

iOS11,safeArea

適配前

iOS11,safeArea

適配后

這樣對于frame布局和autolayout布局的各種情況,有了一個動態的適配方案,就是分別使用safeAreaLayoutGuide和safeAreaInsets來靈活處理布局,相比寫死一個固定距離,當前和未來的各種機型都能一套代碼適配,擴展性更好。我們項目目前也是采用這種做法,如果你的項目需要適配橫豎屏或UI控件布局相對復雜,真的應該考慮使用safeArea

順便提一下,VFL似乎已經廢了,因為|只能表示父view的邊緣,并沒有一個符號來表示父view的safeAreaLayoutGuide的邊緣,以前我們寫的VFL代碼,好多得改,改起來也特別麻煩,建議別再用VFL了

最后一個版本判斷的問題,safeAreaInsets和safeAreaLayoutGuide都是iOS11的API,如果不做封裝,直接在代碼里寫,勢必會出現大量@available這種版本判斷語句,代碼里到處是@available,看起來很崩潰,破壞代碼可讀性。

因為我之前寫了一個自動布局框架,這次就將safeAreaLayoutGuide和版本判斷都順便封裝在里面了,個人覺得這套框架比NSLayoutAnchor好用,主要作用是簡化布局代碼書寫,以下是生成一個NSLayoutConstraint的對比

// 需求是topLeftView的top等于self.view的safeAreaLayoutGuide的top// 使用系統APIif (@available(iOS 11.0, *)) {  [NSLayoutConstraint constraintWithItem:self.topLeftView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view.safeAreaLayoutGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; } else {  [NSLayoutConstraint constraintWithItem:self.topLeftView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; }// 使用NSLayoutConstraint-SSLayoutself.topLeftView.top_attr = self.view.top_attr_safe

在業務代碼里不會出現任何版本判斷,大家有興趣的話可以試一下

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到IOS開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美成人一二三| 久久亚洲精品网站| 国产成人亚洲综合91精品| 伊人久久男人天堂| 91精品久久久久久久久久入口| 国产欧美中文字幕| 中文字幕在线观看亚洲| 久久人91精品久久久久久不卡| 亚洲女人被黑人巨大进入al| 亚洲日韩中文字幕在线播放| 欧美黄色片在线观看| 久久精品久久精品亚洲人| 久久久999国产| 亚洲欧洲免费视频| 亚洲午夜精品久久久久久久久久久久| 国产原创欧美精品| 欧美激情伊人电影| 国产精品高潮在线| 中文字幕一精品亚洲无线一区| 激情久久av一区av二区av三区| 丝袜美腿亚洲一区二区| 国产成人精品一区二区在线| 欧美限制级电影在线观看| 亚洲高清一二三区| 欧美在线一级视频| 综合av色偷偷网| 亚洲国产精品久久久久| 亚洲裸体xxxx| 国产色综合天天综合网| 国产一区二区三区四区福利| 国产午夜精品美女视频明星a级| 日韩免费观看av| 91色琪琪电影亚洲精品久久| 欧美日韩福利电影| 色青青草原桃花久久综合| 久久免费视频网| 日本免费久久高清视频| 久久久久久亚洲精品不卡| 国产原创欧美精品| 日韩欧美亚洲综合| 热久久免费国产视频| 欧洲精品久久久| 高清在线视频日韩欧美| 亚洲黄色www| 91精品国产综合久久香蕉922| 欧美精品福利视频| 欧美亚洲国产视频小说| 97在线看免费观看视频在线观看| 亚洲国产精品一区二区久| 亚洲福利影片在线| 中文字幕久久久| 亚洲欧美激情精品一区二区| 久久久亚洲天堂| 美女视频黄免费的亚洲男人天堂| 日本国产精品视频| 亚洲一区中文字幕在线观看| 成人欧美在线观看| 亚洲天堂成人在线| 97热在线精品视频在线观看| 日韩精品在线观看视频| 亚洲国产成人精品久久| 色综合视频一区中文字幕| 91久久精品美女| 亚洲精品一区中文| 久久久免费观看| xvideos成人免费中文版| 日韩av在线免费播放| 国产乱肥老妇国产一区二| 亚洲人成欧美中文字幕| 亚洲最大成人免费视频| 日韩精品在线电影| 欧美极品美女视频网站在线观看免费| 亚洲综合中文字幕68页| 18一19gay欧美视频网站| 国产精品女人网站| 国产精品永久在线| 日韩风俗一区 二区| 亚洲国产女人aaa毛片在线| 91精品美女在线| 国产精品白丝jk喷水视频一区| 成人免费在线视频网址| 欧美日韩爱爱视频| 欧美巨乳美女视频| 91在线观看免费| 欧美精品亚州精品| 中文字幕综合一区| 日韩欧美在线中文字幕| 欧美夜福利tv在线| 国产一区在线播放| 91福利视频网| 精品成人av一区| 一本色道久久88综合亚洲精品ⅰ| 日韩欧美福利视频| 亚洲第一网站免费视频| 日韩av最新在线| 欧美日韩日本国产| 欧美在线性爱视频| 欧美激情区在线播放| 国产亚洲欧美视频| 91影院在线免费观看视频| 欧美成人国产va精品日本一级| 久久久91精品国产| 国产亚洲xxx| 欧美交受高潮1| 中文字幕国产日韩| 日韩高清不卡av| 久久伊人91精品综合网站| 国产精品白嫩初高中害羞小美女| 欧美性一区二区三区| 国产精品热视频| 日韩av电影在线免费播放| 日韩网站免费观看| 亚洲女同精品视频| 亚洲精品乱码久久久久久按摩观| 国产精品偷伦免费视频观看的| 日韩精品中文字幕有码专区| 一本大道亚洲视频| 欧美激情a∨在线视频播放| 色小说视频一区| 久久91精品国产91久久久| 欧美在线一级va免费观看| 亚洲第一福利视频| 国产精品久久99久久| 精品在线欧美视频| 精品久久久在线观看| 国产香蕉精品视频一区二区三区| 欧美xxxx做受欧美.88| 欧美大片在线影院| 国产噜噜噜噜噜久久久久久久久| 国产欧美最新羞羞视频在线观看| 日韩精品福利在线| 国a精品视频大全| 亚洲国产精彩中文乱码av在线播放| 米奇精品一区二区三区在线观看| 日韩精品视频中文在线观看| 欧美专区第一页| 久久伊人精品一区二区三区| 欧美日韩激情小视频| 国产精品国产三级国产aⅴ9色| 国产精品999| 欧美电影免费观看网站| 亚洲精品影视在线观看| 国产精品黄色影片导航在线观看| 欧美性生交xxxxxdddd| 日韩美女免费观看| 中文字幕日韩专区| 国产偷国产偷亚洲清高网站| 亚洲国产欧美一区| 中文字幕免费精品一区| 国产精品草莓在线免费观看| 狠狠躁夜夜躁人人爽天天天天97| 国产亚洲aⅴaaaaaa毛片| 日韩欧美成人免费视频| 国产mv免费观看入口亚洲| 国模叶桐国产精品一区| 北条麻妃一区二区在线观看| 欧美极品在线视频| 亚洲自拍小视频| 日韩在线精品一区| 成人精品aaaa网站| 国产精品91免费在线| 亚洲欧美日韩综合| 2019中文字幕全在线观看| 97碰在线观看|