之前一直使用VFL(Visual Format Language)写View的布局,发现有很多东西还没有理解透彻。今天自己做了下回顾。
开始
首先说明下,为什么现在还需要使用VFL。大家知道autolayout布局现在有 Masonry和SnapKit写布局更简单,更优雅。为什么不用呢?因为很多时候我们需要些一些公共的或者复用的组件,如果我们直接使用Masonry或者SnapKit的话,就导致这个组件的依赖性太强了。这个时候建议大家还是直接使用VFL语法比较合适。其实Masonry和Snapkit 也只是对VFL的一层封装。
NSLayoutConstraint 约束对象
VFL 的实现是通过NSLayoutConstraint创建约束
public class NSLayoutConstraint : NSObject {
/* Create an array of constraints using an ASCII art-like visual format string.
*/
public class func constraintsWithVisualFormat(format: String, options opts: NSLayoutFormatOptions, metrics: [String : AnyObject]?, views: [String : AnyObject]) -> [NSLayoutConstraint]
/* This macro is a helper for making view dictionaries for +constraintsWithVisualFormat:options:metrics:views:.
NSDictionaryOfVariableBindings(v1, v2, v3) is equivalent to [NSDictionary dictionaryWithObjectsAndKeys:v1, @"v1", v2, @"v2", v3, @"v3", nil];
*/
// not for direct use
/* Create constraints explicitly. Constraints are of the form "view1.attr1 = view2.attr2 * multiplier + constant"
If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute.
*/
public convenience init(item view1: AnyObject, attribute attr1: NSLayoutAttribute, relatedBy relation: NSLayoutRelation, toItem view2: AnyObject?, attribute attr2: NSLayoutAttribute, multiplier: CGFloat, constant c: CGFloat)
/* If a constraint's priority level is less than UILayoutPriorityRequired, then it is optional. Higher priority constraints are met before lower priority constraints.
Constraint satisfaction is not all or nothing. If a constraint 'a == b' is optional, that means we will attempt to minimize 'abs(a-b)'.
This property may only be modified as part of initial set up. An exception will be thrown if it is set after a constraint has been added to a view.
*/
public var priority: UILayoutPriority
/* When a view is archived, it archives some but not all constraints in its -constraints array. The value of shouldBeArchived informs UIView if a particular constraint should be archived by UIView.
If a constraint is created at runtime in response to the state of the object, it isn't appropriate to archive the constraint - rather you archive the state that gives rise to the constraint. Since the majority of constraints that should be archived are created in Interface Builder (which is smart enough to set this prop to YES), the default value for this property is NO.
*/
public var shouldBeArchived: Bool
/* accessors
firstItem.firstAttribute {==,<=,>=} secondItem.secondAttribute * multiplier + constant
*/
unowned(unsafe) public var firstItem: AnyObject { get }
public var firstAttribute: NSLayoutAttribute { get }
public var relation: NSLayoutRelation { get }
unowned(unsafe) public var secondItem: AnyObject? { get }
public var secondAttribute: NSLayoutAttribute { get }
public var multiplier: CGFloat { get }
/* Unlike the other properties, the constant may be modified after constraint creation. Setting the constant on an existing constraint performs much better than removing the constraint and adding a new one that's just like the old but for having a new constant.
*/
public var constant: CGFloat
/* The receiver may be activated or deactivated by manipulating this property. Only active constraints affect the calculated layout. Attempting to activate a constraint whose items have no common ancestor will cause an exception to be thrown. Defaults to NO for newly created constraints. */
@available(iOS 8.0, *)
public var active: Bool
/* Convenience method that activates each constraint in the contained array, in the same manner as setting active=YES. This is often more efficient than activating each constraint individually. */
@available(iOS 8.0, *)
public class func activateConstraints(constraints: [NSLayoutConstraint])
/* Convenience method that deactivates each constraint in the contained array, in the same manner as setting active=NO. This is often more efficient than deactivating each constraint individually. */
@available(iOS 8.0, *)
public class func deactivateConstraints(constraints: [NSLayoutConstraint])
}
VFL 语法
语法 | 解释 |
---|---|
H:|-10-[button]-0-| |
button 水平方向(竖直方向为V)离父视图左边距离为10,右边为0 |
[button1]-[button2] |
button1 和button2 相邻(间隙为0) |
[button1]-10-[button2] |
button1 和button2 相邻(间隙为10) |
[button1]->=10-[button2] |
button1 和button2 的间隙 >= 10 |
[button(50)] |
button 的宽度或者高度为50(取决于H或者V) |
[button(50@750)] |
button 的宽度或者高度为50,优先级为750 |
[button1(==button2)] |
button1 的高度或者宽度等于button2 |
[button(>=20,<=60)] |
button 的高度或者宽大于等于20并且小于等于60 |
来看一串简单的代码:
self.view.addSubview(button1)
self.view.addSubview(button2)
button1.translatesAutoresizingMaskIntoConstraints = false
button2.translatesAutoresizingMaskIntoConstraints = false
let views = ["button1":button1,"button2":button2]
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-10-[button1(30)]->=30-[button2(30)]", options: NSLayoutFormatOptions(rawValue:0), metrics: nil, views: views))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-value-[button1(30)]", options: NSLayoutFormatOptions(rawValue:0), metrics: ["value":20], views: views))
self.view.addConstraint(NSLayoutConstraint(item: button2, attribute: .CenterY, relatedBy: .Equal, toItem: button1, attribute: .CenterY, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: button2, attribute: .Height, relatedBy: .Equal, toItem: button1, attribute: .Height, multiplier: 1, constant: 0))
说明
- 添加约束前视图必须已经加入到了父视图(addSubview)
- 视图的translatesAutoresizingMaskIntoConstraints 属性必须设置为false
/* By default, the autoresizing mask on a view gives rise to constraints that fully determine
the view's position. This allows the auto layout system to track the frames of views whose
layout is controlled manually (through -setFrame:, for example).
When you elect to position the view using auto layout by adding your own constraints,
you must set this property to NO. IB will do this for you.
*/
@available(iOS 6.0, *)
public var translatesAutoresizingMaskIntoConstraints: Bool // Default YES
-
H: -10-[button1(30)]->=30-[button2(30)] 水平方向 button1离父视图距离为10,宽度为30,button1与button2的间距>=30,button2的宽度为30 -
(“V: -value-[button1(30)]”, options: NSLayoutFormatOptions(rawValue:0), metrics: [“value”:20], views: views) 竖直方向button1与父视图顶部距离为20,高度为30 - NSLayoutConstraint(item: button2, attribute: .CenterY, relatedBy: .Equal, toItem: button1, attribute: .CenterY, multiplier: 1, constant: 0) 表示button2在y轴的中心和button1相等。
删除约束
self.view.removeConstraints(self.view.constraints)