Autolayout and aligning subviews

Quick question: How would you align all subviews horizontally while keeping their centers aligned using autolayout?

With visual formatting language that is quite easy. The method we are interested in looks like:

class func constraints(
    withVisualFormat format: String,
    options opts: NSLayoutConstraint.FormatOptions = [],
    metrics: [String : Any]?,
    views: [String : Any]
) -> [NSLayoutConstraint]

And we are interested in NSLayoutConstraint.FormatOptions:

// Align all specified interface elements using NSLayoutConstraint.Attribute.left on each.
static var alignAllLeft: NSLayoutConstraint.FormatOptions

// Align all specified interface elements using NSLayoutConstraint.Attribute.right on each.
static var alignAllRight: NSLayoutConstraint.FormatOptions

// Align all specified interface elements using NSLayoutConstraint.Attribute.top on each.
static var alignAllTop: NSLayoutConstraint.FormatOptions

// Align all specified interface elements using NSLayoutConstraint.Attribute.bottom on each.
static var alignAllBottom: NSLayoutConstraint.FormatOptions

// Align all specified interface elements using NSLayoutConstraint.Attribute.leading on each.
static var alignAllLeading: NSLayoutConstraint.FormatOptions

// Align all specified interface elements using NSLayoutConstraint.Attribute.trailing on each.
static var alignAllTrailing: NSLayoutConstraint.FormatOptions

// Align all specified interface elements using NSLayoutConstraint.Attribute.centerX on each.
static var alignAllCenterX: NSLayoutConstraint.FormatOptions

// Align all specified interface elements using NSLayoutConstraint.Attribute.centerY on each.
static var alignAllCenterY: NSLayoutConstraint.FormatOptions

// Align all specified interface elements using the last baseline of each one.
static var alignAllLastBaseline: NSLayoutConstraint.FormatOptions

Using the simple VFL wrapper, we can play around with these values. So, if we have three subviews of different sizes added to parent view:

let vfl = VFL()
vfl
    .setParent(self)
    .add(subview: VFLColorView(color: .random), name: "redVw")
    .add(subview: VFLColorView(color: .random), name: "blueVw")
    .add(subview: VFLColorView(color: .random), name: "greenVw")

We can create 2 set of constraints, one for horizontal layout and another for vertical layout

vfl
    .storeConstraints(
        formats: [
            "H:|[redVw(30)][blueVw(60)][greenVw(90)]",
            "V:|-(>=100)-[redVw(30)]",
            "V:|-(>=100)-[blueVw(60)]",
            "V:|-(>=100)-[greenVw(90)]"
        ],
        name: "h_layout"
    )
    .storeConstraints(
        formats: [
            "H:|-(>=100)-[redVw(30)]",
            "H:|-(>=100)-[blueVw(60)]",
            "H:|-(>=100)-[greenVw(90)]",
            "V:|[redVw(30)][blueVw(60)][greenVw(90)]"
        ],
        name: "v_layout"
    )

And then we can apply the constraints based on the alignment type:

let alignment: NSLayoutConstraint.FormatOptions
let vLayouts: NSLayoutConstraint.FormatOptions = [
    .alignAllLeft, .alignAllCenterX, .alignAllRight
]
let constraintsName = vLayouts.contains(alignment) ? "v_layout" : "h_layout"
      
vfl
    .removeAllConstraints()
    .removeAllOptions()
    .addOptions(alignment)
    .applyConstraints(name: constraintsName)

Here are the outputs for all various alignment types:

Left CenterX Right Top CenterY Bottom

As an extra bonus, if we with to have the changes always animated all we need to add is:

vfl
    .removeAllConstraints()
    .removeAllOptions()
    .addOptions(alignment)
    .applyConstraints(name: constraintsName)
      
UIView.animate(withDuration: 0.2) {
    self.vfl.parentView?.layoutIfNeeded()
}

Bottom

The full source code is available at: https://github.com/chunkyguy/VFL