gfpacheco
gfpacheco

Reputation: 3225

UIStackView children equally spaced (around and between)

How can I have an UIStackView with the same space as padding and gap between views?

How can I achieve this layout:

Expected layout

When this one doesn't suit me:

No good layout

Neither does this:

enter image description here

I just need the around views space to be the same as the between views space. Why is it so hard?

Important I'm using my fork of TZStackView to support iOS 7. So no layoutMargins for me :(

Upvotes: 7

Views: 3627

Answers (3)

Jayson
Jayson

Reputation: 1687

I know this is an older question, but the way I solved it was to add two UIViews with zero size at the beginning and end of my stack, then use the .equalSpacing distribution.

Note: this only guarantees equal around spacing along the main axis of the stack view (i.e. the left and right edges in my example)

let stack = UIStackView()

stack.axis         = .horizontal
stack.alignment    = .center
stack.distribution = .equalSpacing

// add content normally
// ...

// add extra views for spacing
stack.insertArrangedSubview(UIView(), at: 0)
stack.addArrangedSubview(UIView())

enter image description here

Upvotes: 10

gfpacheco
gfpacheco

Reputation: 3225

For those who keep getting here looking for a solution for this problem. I found that the best way (in my case) would be to use a parent UIView as background and padding, like this:

enter image description here

In this case the UIStackView is contrained to the edges of the UIView with a padding and separate the subviews with spacing.

Upvotes: 2

joern
joern

Reputation: 27620

You can almost achieve what you want using a UIStackView. When you set some constraints yourself on the UIViews inside the UIStackView you can come up with this:

enter image description here

This is missing the left and right padding that you are looking for. The problem is that UIStackView is adding its own constraints when you add views to it. In this case you can add top and bottom constraints to get the vertical padding, but when you try to add a trailing constraint for the right padding, UIStackView ignores or overrides that constraint. Interestingly adding a leading constraint for the left padding works.

But setting constraints on UIStackView's arranged subviews is not what you want to do anyway. The whole point of using a UIStackView is to just give it some views and let UIStackView handle the rest.

To achieve what you are trying to do is actually not too hard. Here is an example of a UIViewController that contains a custom stack view that can handle padding on all sides (I used SnapKit for the constraints):

import UIKit
import SnapKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let padding: CGFloat = 30

        let customStackView = UIView()
        customStackView.backgroundColor = UIColor(white: 0, alpha: 0.1)
        view.addSubview(customStackView)
        customStackView.snp_makeConstraints { (make) -> Void in
            make.top.left.equalTo(padding)
            make.right.equalTo(-padding)
        }

        // define an array of subviews
        let views = [UIView(), UIView(), UIView()]

        // UIView does not have an intrinsic contentSize
        // so you have to set some heights
        // In a real implementation the height will be determined
        // by the views' content, but for this example
        // you have to set the height programmatically
        views[0].snp_makeConstraints { (make) -> Void in
            make.height.equalTo(150)
        }
        views[1].snp_makeConstraints { (make) -> Void in
            make.height.equalTo(120)
        }
        views[2].snp_makeConstraints { (make) -> Void in
            make.height.equalTo(130)
        }

        // Iterate through the views and set the constraints
        var leftHandView: UIView? = nil
        for view in views {
            customStackView.addSubview(view)
            view.backgroundColor = UIColor(white: 0, alpha: 0.15)

            view.snp_makeConstraints(closure: { (make) -> Void in
                make.top.equalTo(padding)
                make.bottom.lessThanOrEqualTo(-padding)
                if let leftHandView = leftHandView {
                    make.left.equalTo(leftHandView.snp_right).offset(padding)
                    make.width.equalTo(leftHandView)
                } else {
                    make.left.equalTo(padding)
                }
                leftHandView = view
            })
        }

        if let lastView = views.last {
            lastView.snp_makeConstraints(closure: { (make) -> Void in
                make.right.equalTo(-padding)
            })
        }
    }
}

This produces the following results:

enter image description here

Upvotes: 7

Related Questions