egze egze - 8 months ago 23
iOS Question

NSLayoutConstraint for unknown number of subviews in code

I'm getting into iOS programming and mastered more or less autolayout with fixed number of items. Say, we have a UILabel for title, UILabel for subtitle, then the visual format for the constraint is

'V:|-[title]-10-[subtitle]-|'


But what if I create subviews dynamically based on some API response. There are for example 40 subviews I need to add. It's not realistic anymore that I specify each subview with the visual format and keep track of them. What is the the proper way?

I imagine that after each subview I add, I then set the constraint based on the previous view with
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
. Is that the way to go, or there is a better way?

Answer

It's not realistic anymore that I specify each subview with the visual format and keep track of them

Why on earth not? You seem to think this is some sort of "either/or" situation. Nothing prevents you from using visual formatting to build up a collection of constraints. No law says that you have to put all your constraints into one visual format.

Consider this code where I build up the constraints for a scroll view and thirty labels inside it:

    var con = [NSLayoutConstraint]()
    con.appendContentsOf(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "H:|[sv]|",
            options:[], metrics:nil,
            views:["sv":sv]))
    con.appendContentsOf(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "V:|[sv]|",
            options:[], metrics:nil,
            views:["sv":sv]))
    var previousLab : UILabel? = nil
    for i in 0 ..< 30 {
        let lab = UILabel()
        // lab.backgroundColor = UIColor.redColor()
        lab.translatesAutoresizingMaskIntoConstraints = false
        lab.text = "This is label \(i+1)"
        sv.addSubview(lab)
        con.appendContentsOf(
            NSLayoutConstraint.constraintsWithVisualFormat(
                "H:|-(10)-[lab]",
                options:[], metrics:nil,
                views:["lab":lab]))
        if previousLab == nil { // first one, pin to top
            con.appendContentsOf(
                NSLayoutConstraint.constraintsWithVisualFormat(
                    "V:|-(10)-[lab]",
                    options:[], metrics:nil,
                    views:["lab":lab]))
        } else { // all others, pin to previous
            con.appendContentsOf(
                NSLayoutConstraint.constraintsWithVisualFormat(
                    "V:[prev]-(10)-[lab]",
                    options:[], metrics:nil,
                    views:["lab":lab, "prev":previousLab!]))
        }
        previousLab = lab

I'm using visual formatting, but I'm doing it one constraint as a time as I add views (which is exactly what you're asking about).