當同事問到我這個問題時,我腦子中直接冒出了一個詞“彈性盒子”。
問題:
有一個 Cell 中有 4 個並排排列的控件,布局如下圖所示:
假設:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD48cD4gPC9wPjxwPjGhoiAgICAgICAg1eLQqb/YvP6437bIus151/ix6rnMtqihozwvcD48cD4yoaIgICAgICAgIMC2yau/2Lz+eM671sO5zLaoo6y1q9PStsu21Mbr09q62smrv9i8/qGjPC9wPjxwPjOhoiAgICAgICAgutrJq6GiuuzJq6GiwszJq7/YvP6/7bbIucy2qKOs09K2y7bUxuvT2tPSsuC1xL/YvP6jqMLMyau/2Lz+09K21Mbr09pjZWxsILXE09Kx36OpoaM8L3A+PHA+IDwvcD48cD7Sqsfzo7o8L3A+PHA+MaGiICAgICAgICC1sbrayauhorrsyauhosLMyau/2Lz+1tC1xMjO0uLSu7j2v9i8/tL+stjKsaOsxuTT4MG9uPa/2Lz+19S2r9PS0sbVvL7d0v6y2L/YvP61xL/YvP6jrMC2yau/2Lz+1PLX1LavsrzC+sqjz8K1xL/ttsiho9LUz8LKx7fWsfDS/rLYxuTW0NK7uPa/2Lz+tcTQp7n7o7o8L3A+PGltZyBzcmM9"/uploadfile/2015/1213/20151213030114863.jpg" alt="\" />
2、 依次類推,當隱藏其中任意2個控件和3個控件全都隱藏的效果如下圖所示:
如果是 HTML5,這個問題用“彈性盒子”來解決是再合適不過了。但是“彈性盒子”是 CSS 3.0中新增的內容,iOS 並不支持彈性盒子,我們只能自己來解決這個問題。
幸好 iOS 有自動布局,我們可以用自動布局來解決這個問題(當然還需要一點點代碼)。
一、 UI 設計
打開故事板,向viewcontrollerz中拖入4個UIView,和3個按鈕,如下圖所示:
這個4個 UIView 和 3個 UIButton 分別是干什麼的,相信你已經能一目了然了。按鈕先不管,先看看4個View。
藍色view的自動布局約束是這樣的:
top:24,leading:16,height:24,trailing:10
黑色、紅色、綠色 view 的布局約束都是一樣的:
width:37,height:24,top:24,trailing:10
四個UIView 分別連接至如下 IBOutlet:
藍色 v1
黑色 v2
紅色 v3
綠色 v4
三個按鈕的點擊事件則分別連接到三個IBAction:
@IBActionfunc hideGray(sender: AnyObject) {
hide(v2)
}
@IBAction func hideRed(sender: AnyObject) {
hide(v3)
}
@IBAction func hideGreen(sender: AnyObject) {
hide(v4)
}
hide()方法待會介紹。
一、 彈性盒子設計
當黑色、紅色、綠色view隱藏時(即hidden 屬性為true),自動釋放其占據的空間,我們需要讓它們的布局約束根據hidden屬性進行改變。
從上面我們可以得知,它們的自動布局約束主要是如下幾個:
width、height、leading、trailing。
這幾個布局跟View所占據的空間有密切關系。其中,height我們不用管,因為它們當width=0 時它們的占據的空間就已經釋放了,height值是多少就無關緊要了。
那麼也就是說,當view隱藏時,我們讓view的width、leading、trailing同時為0,就釋放了view所占據的空間。
因此,我們需要在運行時獲取width、leading、trailing這三個約束,並根據hidden屬性修改它們。那麼我們能夠在運行時獲得View的指定約束嗎?答案是肯定的。
我們知道,UIView有一個 constraints()方法,返回一個NSLayoutConstraints數組,包含了其所有的width、height是屬於view的constrains,而leading、trailing則是屬於superview的。我們可以通過遍歷這兩個數組來找到我們想要的約束。
我們用一個UIView的擴展來實現這個目的:
extension UIView{
func widthConstraint()->NSLayoutConstraint?{
for constraint in self.constraints() {
let firstItem = constraint.firstItem as? UIView
if firstItem == self && constraint.firstAttribute ==NSLayoutAttribute.Width{
println("I gotit:\(constraint)")
return constraint as?NSLayoutConstraint
}
}
return nil
}
func leadingConstraint()->NSLayoutConstraint?{
if self.superview == nil {
return nil
}
for constraint in self.superview!.constraints() {// 這個約束是在superview 中了
let firstItem = constraint.firstItem as? UIView
let secondItem = constraint.secondItem as? UIView
if firstItem == self && constraint.firstAttribute ==NSLayoutAttribute.Leading{
println("I gotit:\(constraint)")
return constraint as?NSLayoutConstraint
}
}
return nil
}
func trailingConstraint()->NSLayoutConstraint?{
if self.superview == nil {
return nil
}
for constraint in self.superview!.constraints() {// 這個約束是在superview 中了
let firstItem = constraint.firstItem as? UIView
if firstItem == self && constraint.firstAttribute ==NSLayoutAttribute.Trailing{
println("I gotit:\(constraint)")
return constraint as?NSLayoutConstraint
}
}
return nil
}
}
然後我們來設計一個彈性盒子,用來管理這三個View。彈性盒子類的主要目的,是將這些View的三個約束的值保存到一個地方(比如說字典中),然後當某個View的hidden屬性設為false時,將約束恢復至原來的值並顯示出來。
class FlexibleBox:NSObject{
structViewSpace:Printable{
var widthConstant:CGFloat = 0
var leadConstant:CGFloat = 0
var trailConstant:CGFloat = 0
var description: String {
return "width-\(widthConstant)\nleading -
\(leadConstant)\ntrailing- \(trailConstant)"
}
}
var cachedConstraints = [UIView:ViewSpace]()
func addViews(views:[UIView]){
for view in views {
addView(view)
}
}
func addView(v:UIView){
var space = ViewSpace()
if let constraint = v.trailingConstraint() {
space.trailConstant = constraint.constant
}
if let constraint = v.leadingConstraint() {
space.leadConstant = constraint.constant
}
if let constraint = v.widthConstraint() {
space.widthConstant = constraint.constant
}
cachedConstraints[v]=space
println("\(space)")
}
func freeViewSpace(v:UIView){
v.widthConstraint()?.constant = 0
v.leadingConstraint()?.constant = 0
v.trailingConstraint()?.constant = 0
}
func resumeViewSpace(v:UIView){
let space = cachedConstraints[v] ?? ViewSpace()
v.trailingConstraint()?.constant = space.trailConstant
v.leadingConstraint()?.constant = space.leadConstant
v.widthConstraint()?.constant = space.widthConstant
}
deinit{
cachedConstraints.removeAll(keepCapacity: false)
}
}
二、 使用彈性盒子
在View Controller 中聲明一個彈性盒子:
let flexBox = FlexibleBox()
然後在viewDidLoad方法中:
flexBox.addViews([v2,v3,v4])
然後但點擊按鈕時,調用如下方法隱藏(或取消隱藏)一個View:
func toggleViewHiddenStatus(v:UIView){
if v.hidden == false {
flexBox.freeViewSpace(v)
}else{
flexBox.resumeViewSpace(v)
}
v.hidden = !v.hidden
self.view.setNeedsLayout()
}
最後一句self.view.setNeedsLayout()將導致所有自動布局約束被重新計算。