iOS 自动布局和弹性盒子

当同事问到我这个问题时,我脑子中直接冒出了一个词“弹性盒子”。

 

问题:

 

有一个 Cell 中有 4 个并排排列的控件,布局如下图所示:


假设:

 

1、        这些控件高度和y坐标固定。

2、        蓝色控件x位置固定,但右端对齐于黑色控件。

3、        黑色、红色、绿色控件宽度固定,右端对齐于右侧的控件(绿色控件右对齐于cell 的右边)。

 

要求:

1、        当黑色、红色、绿色控件中的任意一个控件隐藏时,其余两个控件自动右移占据隐藏控件的控件,蓝色控件则自动布满剩下的宽度。以下是分别隐藏其中一个控件的效果:


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()将导致所有自动布局约束被重新计算。

 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值