1

I have a horizontal stack view with 3 buttons: Backwards, Play, Forward for a music application. Here is my current code:

self.controlStackView.axis = .horizontal
self.controlStackView.distribution = .equalSpacing
self.controlStackView.alignment = .center
self.controlStackView.spacing = 10.0
self.controlStackView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.controlStackView)

self.controlStackView.topAnchor.constraint(equalTo: self.artworkImageView.bottomAnchor, constant: 10.0).isActive = true
self.controlStackView.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor).isActive = true

What does this is it distributes the button as follows (from the center due to alignment):

[backward] - 10 spacing - [play] - 10 spacing - [forward]

I could increase the spacing but it would still be fixed. So I'm setting the leading and trailing anchor to define a maximum width of the stack view:

self.controlStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
self.controlStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true

What this does to the layout:

[left edge backward] - lots of spaaaaaaace - [centered play] - lots of spaaaaaaace - [right edge forward]

It distributes over the entire width (and there is an .equalSpacing between center to the left and right). But this also is not helpful. Essentially my goal is to have true equal spacing including the edges.

Let's say I have an available width of 100 and my 3 buttons are 10, 20, 10 - which means that there is 60 remaining space that is empty.

I would like it to be distributed like this:

space - [backward] - space - [play] - space [forward] - space

So 4 spaces in between my buttons each space being 15, so we fill the 60 remaining space. I could of course implement padding to the stack view to get the outer space, but this would be quite static and is not equally distributed.

Does anybody know if I can implement it this way that the edges are included into the space distribution?

Thanks

Janosch Hübner
  • 1,584
  • 25
  • 44
  • Add two empty views with width == 0 to the beginning and end of stack view. Not an elegant solution, but it works. – Peter Tretyakov Feb 13 '19 at 09:17
  • 2
    Possible duplicate of [UIStackView; Equal Spacing Between & Outside Elements](https://stackoverflow.com/questions/43045423/uistackview-equal-spacing-between-outside-elements) – pckill Feb 13 '19 at 09:18
  • It does work with the placeholder views :) – Janosch Hübner Feb 13 '19 at 09:21
  • The spacing should be dynamic not constant. If I add more views I need to adjust the outer spacing constant as well and on other devices it may also look different – Janosch Hübner Feb 13 '19 at 10:03

1 Answers1

6

This is really pretty straight-forward, using "spacer" views.

Add one more spacer than the number of buttons, so you have:

spacer - button - spacer - button - spacer

Then, constrain the widths of spacers 2-to-n equal to the width of the first spacer. The stackView will handle the rest!

Here is an example (just needs a viewController in storyboard, the rest is done via code):

class DistributeViewController: UIViewController {

    let stackView: UIStackView = {
        let v = UIStackView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.axis = .horizontal
        v.alignment = .fill
        v.distribution = .fill
        v.spacing = 0
        return v
    }()

    var buttonTitles = [
        "Backward",
        "Play",
        "Forward",
//      "Next",
        ]

    var numButtons = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        // stackView will hold the buttons and spacers
        view.addSubview(stackView)

        // constrain it to Top + 20, Leading and Trailing == 0, height will be controlled by button height
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
            stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0.0),
            stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0.0),
            ])

        // arrays to hold our buttons and spacers
        var buttons: [UIView] = [UIView]()
        var spacers: [UIView] = [UIView]()

        numButtons = buttonTitles.count

        // create the buttons and append them to our buttons array
        for i in 0..<numButtons {
            let b = UIButton()
            b.translatesAutoresizingMaskIntoConstraints = false
            b.backgroundColor = .blue
            b.setTitle(buttonTitles[i], for: .normal)
            buttons.append(b)
        }

        // create the spacer views and append them to our spacers array
        //      we need 1 more spacer than buttons
        for _ in 1...numButtons+1 {
            let v = UIView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .red        // just so we can see them... use .clear for production
            spacers.append(v)
        }

        // addd spacers and buttons to stackView
        for i in 0..<spacers.count {

            stackView.addArrangedSubview(spacers[i])

            // one fewer buttons than spacers, so don't go out-of-range
            if i < buttons.count {
                stackView.addArrangedSubview(buttons[i])
            }

            if i > 0 {
                // constrain spacer widths to first spacer's width (this will make them all equal)
                spacers[i].widthAnchor.constraint(equalTo: spacers[0].widthAnchor, multiplier: 1.0).isActive = true

                // if you want the buttons to be equal widths, uncomment this block
                /*
                if i < buttons.count {
                    buttons[i].widthAnchor.constraint(equalTo: buttons[0].widthAnchor, multiplier: 1.0).isActive = true
                }
                */

            }

        }

    }

}

The results with 3 buttons:

enter image description here enter image description here

and with 4 buttons:

enter image description here enter image description here

and a couple with equal-width buttons:

enter image description here enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86