5

I have a horizontal UIStackView that, by default, looks as follows:

default stack view

The view with the heart is initially hidden and then shown at runtime. I would like to reduce the spacing between the heart view and the account name view.
The following code does the job, but only, when executed in viewDidLoad:

stackView.setCustomSpacing(8, after: heartView)

When changing the custom spacing later on, say on a button press, it doesn't have any effect. Now, the issue here is, that the custom spacing is lost, once the subviews inside the stack view change: when un-/hiding views from the stack view, the custom spacing is reset and cannot be modified.

Things, I've tried:

  • verified the spacing is set by printing stackView.customSpacing(after: heartView) (which properly returns 8)
  • unsuccessfully ran several reload functions:
    • stackView.layoutIfNeeded()
    • stackView.layoutSubviews()
    • view.layoutIfNeeded()
    • view.layoutSubviews()
    • viewDidLayoutSubviews()

How can I update the custom spacing of my stack view at runtime?

LinusGeffarth
  • 27,197
  • 29
  • 120
  • 174
  • Are you calling on the main thread? – andym Mar 15 '18 at 18:11
  • @andym, yes, I am. – LinusGeffarth Mar 15 '18 at 18:12
  • 1
    Just my initial thought, but I'm afraid I have no further suggestions, sorry :-( – andym Mar 15 '18 at 18:13
  • When/where are you setting the heart view to not be hidden? – Callam Mar 15 '18 at 18:33
  • If the user activates a certain app-mode, different controls all across the app are being displayed. One of which is the heart view. Since the user can change the app-mode at any time, I need to add the heart view at that point and then set the custom spacing. After switching the `UISwitch`, the app-mode changes and the controls are being updated. – LinusGeffarth Mar 15 '18 at 18:34
  • Okay, so the screenshot you've added is showing the stack view with common spacing? And I'm guessing the custom spacing is to group the heart and account name closer together, is that right? – Callam Mar 15 '18 at 18:45
  • Exactly. However, I'd rather not use workarounds such as creating another stack view, if that's on your mind. – LinusGeffarth Mar 15 '18 at 18:46
  • Ah, that is exactly what I was going to suggest haha – my next thought which is also a bit of a workaround, but could actually introduce the desired effect, is to put the change of `isHidden` and call of `setCustomSpacing` inside a `UIView.animate(withDuration: _, ...)` – Callam Mar 15 '18 at 23:53

3 Answers3

16

You need to make sure the UIStackView's distribution property is set to .fill or .fillProportionally.


I created the following swift playground and it looks like I am able to use setCustomSpacing at runtime with random values and see the effect of that.

import UIKit
import PlaygroundSupport

public class VC: UIViewController {

    let view1 = UIView()
    let view2 = UIView()
    let view3 = UIView()
    var stackView: UIStackView!

    public init() {
        super.init(nibName: nil, bundle: nil)
    }

    public required init?(coder aDecoder: NSCoder) {
        fatalError()
    }

    public override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        view1.backgroundColor = .red
        view2.backgroundColor = .green
        view3.backgroundColor = .blue

        view2.isHidden = true

        stackView = UIStackView(arrangedSubviews: [view1, view2, view3])
        stackView.spacing = 10
        stackView.axis = .horizontal
        stackView.distribution = .fillProportionally

        let uiSwitch = UISwitch()
        uiSwitch.addTarget(self, action: #selector(onSwitch), for: .valueChanged)

        view1.addSubview(uiSwitch)
        uiSwitch.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            uiSwitch.centerXAnchor.constraint(equalTo: view1.centerXAnchor),
            uiSwitch.centerYAnchor.constraint(equalTo: view1.centerYAnchor)
        ])

        view.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            stackView.heightAnchor.constraint(equalToConstant: 50),
            stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 50),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -50)
        ])
    }

    @objc public func onSwitch(sender: Any) {
        view2.isHidden = !view2.isHidden
        if !view2.isHidden {
            stackView.setCustomSpacing(CGFloat(arc4random_uniform(40)), after: view2)
        }
    }
}

PlaygroundPage.current.liveView = VC()
PlaygroundPage.current.needsIndefiniteExecution = true
LinusGeffarth
  • 27,197
  • 29
  • 120
  • 174
Fabio Felici
  • 2,841
  • 15
  • 21
  • So, you gave me the hint I needed: `stackView.distribution = .fillProportionally`. My stack view's distribution was set to `equalSpacing`, which already sounds wrong in this context, ha! Thanks! – LinusGeffarth Mar 16 '18 at 07:39
9

Another reason setCustomSpacing can fail is if you call it before adding the arranged subview after which you want to apply the spacing.

Won't work:

headerStackView.setCustomSpacing(50, after: myLabel)
headerStackView.addArrangedSubview(myLabel)

Will work:

headerStackView.addArrangedSubview(myLabel)
headerStackView.setCustomSpacing(50, after: myLabel)
Eric
  • 16,003
  • 15
  • 87
  • 139
0

I also noticed that custom spacing values get reset after hiding/unhiding children. I was able to override updateConstraints() for my parent view and set the custom spacing as needed. The views then kept their intended spacing.

override func updateConstraints() {
  super.updateConstraints()
  stackView.setCustomSpacing(10, after: childView)
}
Jay Whitsitt
  • 937
  • 9
  • 27