This evening I found myself staring at a block of Autolayout code, thinking to myself: “There must be a better way”. Of course, the ideal is to do all your Autolayout in Interface Builder, but every now and then there are times when you just have to use some code. My code looked something like this:
let r = NSLayoutConstraint(item: toolbarView, attribute: .Right, relatedBy: .Equal, toItem: overlayView, attribute: .Right, multiplier: 1.0, constant: 0.0) let l = NSLayoutConstraint(item: toolbarView, attribute: .Left, relatedBy: .Equal, toItem: overlayView, attribute: .Left, multiplier: 1.0, constant: 0.0) let b = NSLayoutConstraint(item: toolbarView, attribute: .Bottom, relatedBy: .Equal, toItem: overlayView, attribute: .Bottom, multiplier: 1.0, constant: 0.0) let t = NSLayoutConstraint(item: toolbarView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100.0) overlayView.addConstraints([l,b,r,t])
I understand Apple’s preference for clarity over terseness but, in this case, I think clarity is suffering at the hands of verbosity. With Swift’s default arguments and ability to suppress parameter labeling I think we can do better. My first attempt to simplify the code was to add the following two functions to my project.
func constraint(item1: UIView, attribute1: NSLayoutAttribute, relation: NSLayoutRelation, item2: UIView, attribute2: NSLayoutAttribute, constant: CGFloat = 0.0, multiplier: CGFloat = 1.0) -> NSLayoutConstraint { return NSLayoutConstraint(item: item1, attribute: attribute1, relatedBy: relation, toItem: item2, attribute: attribute2, multiplier: multiplier, constant: constant) } func constraint(item1: UIView, attribute1: NSLayoutAttribute, relation: NSLayoutRelation, constant: CGFloat = 0.0, multiplier : CGFloat = 1.0) -> NSLayoutConstraint { return NSLayoutConstraint(item: item1, attribute: attribute1, relatedBy: relation, toItem: nil, attribute: .NotAnAttribute, multiplier: multiplier, constant: constant) }
With these in place the code becomes:
let r = constraint(toolbarView, .Right, .Equal, overlayView, .Right) let l = constraint(toolbarView, .Left, .Equal, overlayView, .Left) let b = constraint(toolbarView, .Bottom,.Equal, overlayView, .Bottom) let t = constraint(toolbarView, .Height, .Equal, constant:100.0) overlayView.addConstraints([l,b,r,t])
This is already a great improvement (at least to my eyes), but we can go a little further. There is a lot of repetition of the {swift}toolbarView{/swift} variable in the above code. We can cut this out by extending {swift}UIView{/swift}.
extension UIView { /** :returns: true if v is in this view's super view chain */ public func isSuper(v : UIView) -> Bool { for var s : UIView? = self; s != nil; s = s?.superview { if(v == s) { return true; } } return false } public func constrain(attribute: NSLayoutAttribute, _ relation: NSLayoutRelation, _ otherView: UIView, _ otherAttribute: NSLayoutAttribute, constant: CGFloat = 0.0, multiplier : CGFloat = 1.0) -> UIView? { let c = constraint(self, attribute, relation, otherView, otherAttribute, constant: constant, multiplier: multiplier) if isSuper(otherView) { otherView.addConstraint(c) return self } else if(otherView.isSuper(self) || otherView == self) { self.addConstraint(c) return self } assert(false) return nil } public func constrain(attribute: NSLayoutAttribute, _ relation: NSLayoutRelation, constant: CGFloat, multiplier : CGFloat = 1.0) -> UIView? { let c = constraint(self, attribute, relation, constant: constant, multiplier: multiplier) self.addConstraint(c) return self } }
Finally the code becomes:
toolbarView.constrain(.Right, .Equal, overlayView, .Right)? .constrain(.Left, .Equal, overlayView, .Left)? .constrain(.Bottom, .Equal, overlayView, .Bottom)? .constrain(.Height, .Equal, constant:100.0)
I think this makes it much clear what’s going on.
Pingback: Issue #7 – October 10, 2014 | iOS Dev Newbie
Pingback: [整理]swift的视图的布局的约束 | 在路上