iosswiftvisual-format-language

How to align multiple elements to one main element with format strings in swift


I am trying to perform the following task that is shown with the following image

This is what I have as far as constraints go so far, the "Entertainment" string is represented by the categoryLabel and the "Disney: build it frozen" by nameLabel.

enter image description here

addConstraints(withFormat: "H:|-8-[v0(90)]-8-[v1]-8-|", views: imageView, nameLabel)
addConstraints(withFormat: "V:|-8-[v0(90)]", views: imageView)

addConstraints(withFormat: "H:[v0]-8-[v1]", views: imageView, nameLabel)
addConstraints(withFormat: "V:|-8-[v0]-8-[v1]", views: nameLabel, categoryLabel)

func addConstraints(withFormat format: String, views: UIView...) {
    var viewsDictionary = [String: UIView]()
    for (index, view) in views.enumerated() {
        let key = "v\(index)"
        view.translatesAutoresizingMaskIntoConstraints = false
        viewsDictionary[key] = view
    }

    addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}

The problem I am having is how to add the "categorylabel" to the constraints such that it is aligned to right of the imageView by 8 pixels and directly under the nameLabel by 8 pixels. As you can see from the screenshot I can achieve placing it below the nameLabel but I am having a hard time making it such that the "categorylabel" is to the right of the image view as well.

Any tips would be very much appreciated :)

What I want it to look like enter image description here


Solution

  • Unfortunately by wrapping NSLayoutConstraint.constraints in your own function, you have prevented access to the feature you need, which is to specify the .alignAllLeading NSLayoutFormatOptions to your nameLabel-category constraint.

    Essentially, what you need is:

    NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[v0]-8-[v1]", options: .alignAllLeading, metrics: nil, views: viewsDictionary))
    

    This will tell autolayout that you want the leading edges of the two items to be aligned and separated vertically by 8. Since you already have your name field positioned relative to the edge of the image view, this will put your category field where you want it.

    You can re-factor your addConstraints function to accept NSLayoutFormatOptions:

    func addConstraints(withFormat format: String, options: NSLayoutFormatOptions = NSLayoutFormatOptions(), views: UIView...) {
    
    var viewsDictionary = [String: UIView]()
        for (index, view) in views.enumerated() {
            let key = "v\(index)"
            view.translatesAutoresizingMaskIntoConstraints = false
            viewsDictionary[key] = view
        }
    
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: options, metrics: nil, views: viewsDictionary))
    }
    

    and then call it:

    addConstraints(withFormat: "V:|-8-[v0]-8-[v1]", options: .alignAllLeading, views: nameLabel, categoryLabel)
    

    By the way, you don't need to use storyboards to use stack views.