這裏有一篇關於YogaKit使用方法的翻譯文章,其中介紹的比較全面,基本可使用FlexBox佈局,可是僅僅看這裏的介紹仍是難以解決一些佈局問題,本篇文章再也不介紹 FlexBox 和 YogaKit 的相關知識和概念,直奔主題實踐,旨在經過案例幫助更好的解決問題。swift
在學習FlexBox佈局的時候,爲了加深理解建議學習一下CSS,理解盒子模型、外邊距、內邊距、定位、定位上下文及相對定位和絕對定位的區別,可以幫助咱們更好的理解FlexBox,更好的使用YogaKit。app
以下圖,這種佈局很常見,實現的方法也有不少,使用YogaKit實現也有不少方法,可是優雅程度不一樣。佈局
首先,使用 preservingOrigin 能夠勉強實現這一佈局,可是代碼顯得不規整,做爲一種佈局方法,我直接摒棄。post
let blueView = UIView(frame: .zero)
blueView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.marginTop = 60
}
view.addSubview(blueView)
let imageView = UIImageView(frame: .zero)
imageView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 100
layout.marginTop = 10
}
view.addSubview(imageView)
view.yoga.applyLayout(preservingOrigin: true)
imageView.yoga.applyLayout(preservingOrigin: false)
複製代碼
其次,使用 margin 屬性,將其賦負值也能夠輕易實現此效果,然而這些都不是重點學習
let blueView = UIView(frame: .zero)
blueView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.marginTop = 60
}
view.addSubview(blueView)
let imageView = UIImageView(frame: .zero)
imageView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 100
layout.aspectRatio = 1
layout.marginTop = -50
layout.alignSelf = .center
}
blueView.addSubview(imageView)
view.yoga.applyLayout(preservingOrigin: true)
複製代碼
問題1:YogaKit是佈局流式佈局的利器,在使用的時候會發現整個佈局都是根據內容依次堆疊,在如圖的佈局樣式中,若是要把 imageView 放到藍色背景的底部,此時就沒法使用 marginBottom 來完成。flex
解決這個問題首先想到的是經過調整父視圖的內容佈局屬性來完成。spa
let blueView = UIView(frame: .zero)
blueView.backgroundColor = .blue
blueView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.marginTop = 60
layout.justifyContent = .flexEnd
}
view.addSubview(blueView)
複製代碼
問題2:若是僅僅經過修改 layout.justifyContent = .flexEnd 來解決此問題就會引發新的問題,就是說會影響藍色View中的其餘內容,全部內容都以底部爲起點開始佈局,故此方案有部分影響,根據狀況採納。翻譯
其次的解決方法能夠是用定位來處理,只能說不要太完美。code
let blueView = UIView(frame: .zero)
blueView.backgroundColor = .blue
blueView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.marginTop = 60
layout.position = .relative
}
view.addSubview(blueView)
let image = UIImageView(frame: .zero)
image.backgroundColor = .yellow
image.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 100
layout.bottom = 0
layout.aspectRatio = 1
layout.alignSelf = .center
layout.position = .absolute
}
blueView.addSubview(image)
view.yoga.applyLayout(preservingOrigin: true)
複製代碼
在項目中可能會遇到這種佈局,使用YogaKit中的FlexWrap能更方便的解決item(圖中圓角方塊)換行問題,可是也存在一些問題,如圖所示,爲了便於說明作如下命名,圖中深色圓角方塊爲 item,圖中圖片爲 image,圖中包裹 item 的邊框爲 wrapper,總體爲 cellcdn
在此中樣式的 cell 中,咱們但願 image 按比例位於 cell 的最右邊,因此 wrapper 的 flexGrow 設爲 1,能夠將 image 擠到最右邊;wrapper 中的 item 要換行,因此 wrapper 的 flexWrap 設爲 wrap,核心代碼以下:
cell.configureLayout { (layout) in
layout.isEnabled = true
layout.flexDirection = .row
}
let wrapper = UIView(frame: .zero)
wrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.flexDirection = .row
}
cell.addSubview()
let image = UIImageView(frame: .zero)
image.configureLayout { (layout) in
layout.isEnabled = true
layout.width = 140
layout.aspectRatio = 1
}
cell.addSubview(image)
for _ in 0..<8 {
let item = UILabel(frame: .zero)
item.configureLayout { (layout) in
layout.isEnabled = true
layout.marginHorizontal = 8
layout.marginVertical = 5
layout.width = 60
layout.height = 30
}
wrapper.addSubview(item)
}
複製代碼
問題1:僅僅如此是不夠的,運行會看到 image 被擠出屏幕外邊,並且 item 們也沒有折行,顯然 wrapper 被內容撐開,未達到預期樣式。
子視圖會影響俯視圖的大小,使用 flexWrap 屬性可讓子視圖折行,可是前提是要給父視圖一個明確的寬度。
let maxWidth = YGValue(view.bounds.size.width - 140)
wrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.flexDirection = .row
layout.width = maxWidth
}
複製代碼
問題2:在給定 wrapper 一個寬度後,貌似能夠完美解決問題,可是前提是須要計算寬度,不夠優雅,在一個寬度不固定的容器內顯然不能使用此方法。
要解決此問題就要用到 position 屬性,position 有兩個值:.relative 相對定位 和 .absolute 絕對定位,相對定位能夠做爲絕對定位的定位上下文,決定絕對定位的參照物,若是沒有定位上下文默認參照物爲根視圖。使用絕對定位使得該視圖脫離佈局流,位置相對於父視圖,不會影響父視圖大小。在 wrapper 中再添加一個 rapWrapper 來承載 item。
let wrapper = UIView(frame: .zero)
wrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.position = .relative
}
cell.addSubview(wrapper)
let rapWrapper = UIView(frame: .zero)
rapWrapper.backgroundColor = .gray
rapWrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexWrap = .wrap
layout.flexDirection = .row
layout.position = .absolute
}
wrapper.addSubview(rapWrapper)
複製代碼
for _ in 0..<8 {
let item = UILabel(frame: .zero)
item.configureLayout { (layout) in
layout.isEnabled = true
layout.marginHorizontal = 8
layout.marginVertical = 5
layout.width = 60
layout.height = 30
}
rapWrapper.addSubview(item)
}
複製代碼
一樣是簡單的佈局,使用YogaKit也很簡單,然而簡單是要付出代價的,正確的使用才能避免沒必要要的尷尬😓
看上去如此簡單的佈局,用YogaKit的時候會感覺到它的"魔性",前提是要正確的使用。
首先想到的佈局方法是 scrollView 使用 flexGrow 充滿,將 button 擠到底部,而 scrollView 的 contentSize 也不用計算了,在內部加一個 contentView 負責填充內容,撐開 scrollView 便可,簡單到堪稱完美。
let scrollView = UIScrollView(frame: .zero)
scrollView.backgroundColor = .blue
scrollView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
}
view.addSubview(scrollView)
let contentView = UIView(frame: .zero)
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 300
}
scrollView.addSubview(contentView)
let button = UIView(frame: .zero)
button.backgroundColor = .yellow
button.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 50
}
view.addSubview(button)
view.yoga.applyLayout(preservingOrigin: true)
scrollView.contentSize.height = contentView.bounds.size.height
複製代碼
問題1:看似很簡單的佈局,也爲之後出錯埋下了伏筆,當 contentView 的高度小時還看不出問題,可是當 contentView 的高度大於屏幕高度時,問題出現了,scrollView 不能滑動,button 也被擠到了屏幕外面。
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 900
}
複製代碼
如此,解決辦法也就顯而易見了,沒錯,使用定位解決問題。
let scrollView = UIScrollView(frame: .zero)
scrollView.backgroundColor = .blue
scrollView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.position = .relative
}
view.addSubview(scrollView)
let contentView = UIView(frame: .zero)
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 900
layout.position = .absolute
}
scrollView.addSubview(contentView)
let button = UIView(frame: .zero)
button.backgroundColor = .yellow
button.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 50
}
view.addSubview(button)
view.yoga.applyLayout(preservingOrigin: true)
scrollView.contentSize.height = contentView.bounds.size.height
複製代碼
感謝 yun1467723561418 這位好心朋友的指點,案例二和案例三中避免子視圖撐大父視圖用 flexShrink 一樣能夠解決,以下:
let wrapper = UIView(frame: .zero)
wrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.flexShrink = 1
}
cell.addSubview(wrapper)
複製代碼
for _ in 0..<8 {
let item = UILabel(frame: .zero)
item.configureLayout { (layout) in
layout.isEnabled = true
layout.marginHorizontal = 8
layout.marginVertical = 5
layout.width = 60
layout.height = 30
}
wrapper.addSubview(item)
}
複製代碼
let scrollView = UIScrollView(frame: .zero)
scrollView.backgroundColor = .blue
scrollView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.flexShrink = 1
}
view.addSubview(scrollView)
let contentView = UIView(frame: .zero)
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 900
}
scrollView.addSubview(contentView)
let button = UIView(frame: .zero)
button.backgroundColor = .yellow
button.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 50
}
view.addSubview(button)
view.yoga.applyLayout(preservingOrigin: true)
scrollView.contentSize.height = contentView.bounds.size.height
複製代碼
flexShrink 容許內容超出時縮小,標記內容超出時誰先縮小,優先級從根視圖依次往下,如:A包含B,B包含C,在沒有限制A的大小時A可能被內容撐開,設置B或者C的 flexShrink 是不起做用的,但設置A的 flexShrink能夠保證B或C不會超出A的大小
不知道使用YogaKit的同窗有多少,也沒有看到有介紹 position 的文章,我也是經過閱讀了「CSS權威指南」後才嘗試使用 position 的,同時對於 overflow 的使用仍是不得而知,但願有懂的大神告訴我一下!有寫的不對的地方,還請慷慨指出!