1. 程式人生 > >iOS layoutMargins 的坑:一個活久見的 bug

iOS layoutMargins 的坑:一個活久見的 bug

神奇的效果

有天一回到座位上,張皇失措的應屆生同事就好像看到救星一樣把我抓過去:“倉薯,不好了,你看它這樣了!!”

我一看,從不說粗口的倉薯也忍不住說了一句:“我……去,我做了這麼多年 iOS 還從來沒遇見這樣的事。” 把領導也叫過來看。領導拿來玩了一會兒,然後說:“哈哈哈,感覺真想要實現這個效果,還不是那麼容易呢……”

究竟是什麼 bug 讓我們都這麼不淡定呢?看下面的 gif 就知道了:

2.gif

這個方塊形的 cell 就是一個平凡而普通的 collectionView 上平凡而普通的 collectionViewCell,很多地方都在用,用了一年多了,一直都長這個樣子,從沒出任何問題。然而被我們的應屆生同事不知道怎麼一改,出現了這樣的效果:當 cell 滾動到螢幕邊緣,即將離開螢幕的時候,它好像捨不得離開一樣,竟然把自己縮起來了……

要不要來幫我 debug

以下是能重現 bug 的程式碼,能在 iPhone 7 iOS 11 模擬器上重現。為了只寫一個檔案,我就把程式碼最簡化了,只要 60 行:

1234567891011121314151617181920212223242526272829303132333435363738394041424344import UIKitfinal class TestCell: UICollectionViewCell {override init(frame: CGRect) {let imageView = UIImageView(frame: .zero)let metadataView = UIView(frame: .zero)
super.init(frame: frame)imageView.backgroundColor = UIColor.redmetadataView.backgroundColor = UIColor.greenfor view in [imageView, metadataView] {addSubview(view)view.translatesAutoresizingMaskIntoConstraints = falseview.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor).isActive = 
trueview.trailingAnchor.constraint(equalTo: self.layoutMarginsGuide.trailingAnchor).isActive = true}imageView.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor).isActive = trueimageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = truemetadataView.topAnchor.constraint(equalTo: imageView.bottomAnchor).isActive = truemetadataView.heightAnchor.constraint(equalToConstant: 25).isActive = truemetadataView.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor).isActive = true}required public init?(coder aDecoder: NSCoder) {fatalError("init(coder:) has not been implemented")}}final class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {override func viewDidLoad() {super.viewDidLoad()self.collectionView!.contentInsetAdjustmentBehavior = .neverself.collectionView!.register(TestCell.self, forCellWithReuseIdentifier: "Cell")}// MARK: UICollectionViewDataSourceoverride func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {return 10}override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {return collectionView.dequeueReusableCell(withReuseIdentifier: "Cell"for: indexPath)}func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {let measurementCell = TestCell()let width = (collectionView.bounds.size.width - 20) / 2.0measurementCell.widthAnchor.constraint(equalToConstant: width).isActive = truereturn CGSize(width: width, height: measurementCell.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height)}}

約束用的是系統原生的寫法,可能大家平時用第三方庫用得多,原生寫法反而不熟悉了。簡單解釋下,假設紅色是圖片,綠色是描述吧:

  1. 圖片左邊、右邊、上面約束到父 view,高度 = 寬度

  2. 描述左邊、右邊、下面約束到父 view,高度固定 25,頂部貼著圖片底部

程式碼出來了,能看出是什麼問題嗎?

幾個猜測

Q:是不是 layout 出什麼問題了!

A:用的是最簡單的 UICollectionViewFlowLayout 啊…… 沒 override 任何東西。

Q:是不是 constraint 衝突?

A:你看我約束得有啥問題?明明不會有任何衝突耶。

Q:Cell size 算得不對吧?

A:最普通的自動計算…… 打 log 來看算得是對的。而且,就算是出了問題,滾動的時候也不會實時計算 size 啊…… 它可是一邊滾一邊縮啊……

Q:view.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor).isActive = true 這個self.layoutMarginsGuide.leadingAnchor是什麼鬼,你就不能用self.leadingAnchor嗎?

A:你猜對了…… 因為想省事改 self.layoutMargins 所以約束到 layoutMarginsGuide,但確實如果改成約束到普通的self.leadingAnchor就不會有問題了。

Q:這貨是不是隻有什麼特定情況才有的 bug,比如 iOS 11 或者 iPhoneX

A:沒錯是 iOS 11 才有……任何手機都可以重現,但確實跟 iPhoneX 有點關係……

這下聰明的讀者猜出是什麼問題了嗎?:)

其實就是少了一行

要解決這個問題很簡單,就是在 cell 的init方法里加一句

1self.insetsLayoutMarginsFromSafeArea = false

insetsLayoutMarginsFromSafeArea 這個屬性對於所有UIView預設為YES(我覺得這點並不是太科學),當它為YES的時候,view 的 layoutMargins 會根據 safeArea 進行調整。這樣的話,即使把 layoutMargins 設定為一個固定值比如 layoutMargins = .zero,但是到了螢幕邊緣的時候,它的 margins 還是會逐漸變大,本意應該是為了讓子 view 自動避開 iPhoneX 的劉海吧。這樣,出現上面這個效果神奇的 bug也不足為怪了。

Layout Margins 的好處和坑

這麼說的話,其實應該是個很常見的問題,為啥平常遇到的不多呢?我想還是因為我們約束到 layoutMarginsGuide 的情況比較少吧。

layoutMargins 這套東西用來改 insets 是非常方便的。比如我寫一個用途很廣泛的東西,希望能支援使用者隨意改動它的 insets,如果我不用 layoutMargins 的話,我需要維護 4 個 constraints:

12345678910111213141516171819// propertiesvar leadingInsetConstraint: NSLayoutConstraint!var trailingInsetConstraint: NSLayoutConstraint!var topConstraint: NSLayoutConstraint!var bottomConstraint: NSLayoutConstraint!// during initself.leadingInsetConstraint = someView.leadingAnchor.constraint(equalTo: self.leadingAnchor)self.leadingInsetConstraint.isActive = 

相關推薦

iOS layoutMargins一個bug

神奇的效果有天一回到座位上,張皇失措的應屆生同事就好像看到救星一樣把我抓過去:“倉薯,不好了,你看它這樣了!!”我一看,從不說粗口的倉薯也忍不住說了一句:“我……去,我做了這麼多年 iOS 還從來沒遇見這樣的事。” 把領導也叫過來看。領導拿來玩了一會兒,然後說:“哈哈哈,感覺

張書樂!王者榮耀不是農藥!拜托,這不過是一個遊戲

王者榮耀今日午後,在香港上市的騰訊控股一度跌5%,目前跌幅近4%。人民網日前發表評論文章稱,《王者榮耀》面向社會不斷在釋放負能量,監管主體有必要讓遊戲多一些“善意”。在很多人看來,這一次是人民網抨擊王者榮耀。我想說,在我看來,這真不是抨擊,而是希望能夠有一個更好的無毒“農藥”,它呼籲的是遊戲廠商的監管,也呼籲

原來 Chrome 瀏覽器支援 Import from 語法

需要滿足以下三個條件: 1、高版本的Chrome ,總而言之越新越好……,其他瀏覽器請參考:https://caniuse.com/#search=import 2、必須在伺服器環境下才能執行,譬如apache或者nginx,或者大部分語言都能開啟的服務環境(我這裡用phpstudy的Nginx環境來測試

原來 Chrome 瀏覽器支持 Import from 語法

圖片 原來 default init ons width 參考 har 在服務器 需要滿足以下三個條件: 1、高版本的Chrome ,總而言之越新越好……,其他瀏覽器請參考:https://caniuse.com/#search=import 2、必須在服務器環境下才能運行

【轉】TeXmacs一個真正“所即所得”的排版系統

min 重新 -c href .org 所見 思維 pac acs TeXmacs:一個真正“所見即所得”的排版系統 好久沒有推薦過自己喜歡的軟件了,現在推薦一款我在美國做數學作業的私家法寶:TeXmacs。我恐怕不可能跟以前那麽有閑心寫個長篇的 TeXmacs 說明文

微軟“黑歷史”一個了 45 年的愚蠢 Bug

微軟於近期推出了Windows10作業系統。不斷壓縮的更新週期下,一般而言,系統Bug的存活希望會被很快掐滅,快速迭代。但是本文的作者在Windows 10上從一個USB 3.0 SSD向另一個SSD拷貝檔案時卻遭遇了一個44年前的bug,他把這個“愚蠢”的Bug分享到了部落格

[王垠系列]TeXmacs一個真正“所即所得”的排版系統

TeXmacs:一個真正“所見即所得”的排版系統 好久沒有推薦過自己喜歡的軟體了,現在推薦一款我在美國做數學作業的私家法寶:TeXmacs。我恐怕不可能跟以前那麼有閒心寫個長篇的 TeXmacs 說明文件了,不過這東西如此的簡單好用,所以基本上不用我寫什麼文件了。

!蘋果開通官方微博啦,可評論巨紮心!

mark 進行 嘗試 信息 市場 term sha ges oss 4月23日上午蘋果開通了官方微博,第一微博就是向國人打招呼“hello”!並且蘋果開通微博一事件在當天還上了熱搜,蘋果本次開通微博的初衷為分享各種實用信息和使用妙招,讓一切更加得心應手。 近兩年來蘋果手機在

[程式人生]那些IT界“”的奇葩現象

        常言道,人活久了什麼稀奇古怪的事都會見到。本文盤點幾件剛畢業工作時想當然,工作若干年後啪啪打臉的“奇葩”事。          (1)去年推薦一朋友來我們公司面試時,朋友說起當年她去某遊戲公司時,那公司H

!Linux命令列居然也可以用來檢視影象?

在 Linux 中有很多 GUI 應用程式可以檢視影象,但是這對經常使用命令列來工作的人可能會覺得很繁瑣。今天要介紹的是 3 個實用的 CLI 影象檢視器來在終端上檢視影象,讓那些使用 CLI 的朋友能更加高效地工作。 1. FIM FIM 是 Fbi IMproved 的縮略語,意思是 Fbi 改進版。 有

一個react拖動排序中的key

map 一個 解決方案 () 不重復 tab 需求 unique nbsp 在做一個基於react的應用的時候遇到了對列表拖動排序的需求。當使用sortable對列表添加排序支持後發現一個問題:數據正確排序了,但是dom的順序卻亂了,找了一會兒原因後發現是因為在渲染數據的時

Windows下使用OpenSSL生成自簽證書(很簡單,一個晚上搞明白的,讓後來者少走彎路)

vat 都是 環境 csr 過程 環境變量 crypt 報錯 out 最近在學習中發現openssl 中有個坑,所有的教程都是openssl genrsa -des3 -out private.key 1024,但是產生的證書,npm start 之後就報錯如下: erro

瀏覽器在短時間內對於同一個請求的處理,會先等待上一個請求完成後,再處理下一個請求,導致在測試異步時誤導代碼有問題。

例子 red count 時間 ble http tab 問題 結束 例子:   tornado後端異步處理(模擬異步處理20秒)      瀏覽器請求接口:http://192.168.1.98:1104/test         然後又打開一個table,請求同一個接口

Java遇到的第一個base64編碼

    最近做專案,後臺使用了Tomcat作為伺服器,使用Java進行開發。個人對Java沒有系統的學習過,現在也算是邊學邊做吧(不過有很厚的C/C++經驗)。前後臺傳遞訊息的時候,用到了Base64編碼,前端使用C#編寫的Demo。開始時,後端Base64編解碼時使用了sun.misc.

已知有十六支男子足球隊參加2008 北京奧運會。寫一個程式,把這16 支球隊隨機分為4 個組。 注參賽球隊列表附錄 注2使用Math.random 來產生隨機數。(也可以使用其它方法) 2. 2

/** * Created by whp on 2018/7/30. */ public class Test { public static void main(String[] args) { String[] str={"象牙海岸","阿根廷","澳大利亞","塞爾

一個 -bash: ./backup.sh: /bin/bash^M: bad interpreter: No such file or directory 由於shell指令碼檔案被我在Windows下編輯過,出現上面錯誤的原因之一是指令碼檔案是DOS格式的, 即每一行的行尾以\r\n來標識

    由於shell指令碼檔案被我在Windows下編輯過,出現上面錯誤的原因之一是指令碼檔案是DOS格式的, 即每一行的行尾以\r\n來標識, 使用vim編輯器開啟指令碼, 執行::set ff? 可以看到DOS或UNIX的字樣. 使用se

朱曄和你聊Spring系列S1E8著用的Spring Cloud(含一個實際業務貫穿所有元件的完整例子)

本文會以一個簡單而完整的業務來闡述Spring Cloud Finchley.RELEASE版本常用元件的使用。如下圖所示,本文會覆蓋的元件有: Spring Cloud Netflix Zuul閘道器伺服器 Spring Cloud Netflix Eureka發現伺服器 Spring Cloud Net

實際工作場景中踩過redis的一個不查詢redis,而查詢後端資料庫問題

      今天,在工作中遇到專案在查詢碼錶字典轉換時,不走redis而是查詢後臺資料庫問題。做一個簡單記錄,目的是防止以後出現類似問題,同時該碼錶存在快取中採用的資料型別也是值得我學習的。 一、簡單的背景介紹       該專案以

輪子一個簡單的node爬蟲踩之路

一個簡單的node爬蟲踩坑之路 準備工作 最近在看爬蟲相關的文章,偶然想起來嘗試一下用node來實現一個簡單的爬蟲。但是爬別的多沒意思,當然是爬美女圖片啊。。。 這大概 node 裡面造的最多的輪子了。 於是,我選取了下面的地址:美女圖片戳我,簡單分析後

Flutter新手第一個Could not find com.android.tools.lint:lint-gradle:26.1.1.

解決方法1:修改build.gradle,註釋掉jcenter(),google()。使用阿里的映象。原因是jcenter google庫無法訪問到導致的問題。雖然我有萬能的爬牆工具,開啟全域性代理依然被我們偉大的發改委牆掉了! buildscript { repositories { //goo