iosswiftuistackviewspacingproportions

Custom proportional spacing in UIStackView without of additional subviews?


I know that I can add transparent subviews to UIStackView and set their widths with constraints. The problem is if I want to hide some opaque subviews all the spaces between them remain (and I don't know how to "disable" these spaces properly).

I found setCustomSpacing(_:after:):

https://developer.apple.com/documentation/uikit/uistackview/2866023-setcustomspacing

The problem is it sets constant spacing but in my case opaque subviews have constant width and spacing between them varies.

Then I found customSpacing(after:) and tried to override it:

https://developer.apple.com/documentation/uikit/uistackview/2865827-customspacing

It seems to be the most appropriate solution for me but the problem is the overriden method is never called.

So are there normal ways to add subviews into UIStackView with proportional spacing and allow to hide these subviews simultaneously with their spacing?


Solution

  • If your spacings varies dynamically, depending on the dimensions of other views, set the spacings in layoutSubviews, or viewDidLayoutSubviews if you are in a VC. These are the methods that will be called when some view's dimensions change. There, you can do whatever calculations you like, and compute a value for each view's spacing.

    For example, here I have created a stack view with 3 labels. The spacing between the first and second labels is equal to 1/10 of the stack view's width, and the spacing between the second and third labels is equal to 1/20 of the stack view's width (aka one half of the first).

    var stack: UIStackView!
    
    override func viewDidLoad() {
        func makeLabel(_ text: String) -> UILabel {
            let label = UILabel(frame: .zero)
            label.text = text
            label.backgroundColor = .green
            label.sizeToFit()
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }
        stack = UIStackView(arrangedSubviews: [makeLabel("Foo Bar"), makeLabel("Something"), makeLabel("Else")])
        stack.axis = .horizontal
        stack.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stack)
        NSLayoutConstraint.activate([
            view.centerYAnchor.constraint(equalTo: stack.centerYAnchor),
            stack.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            stack.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10)
        ])
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        stack.setCustomSpacing(stack.bounds.width / 10, after: stack.arrangedSubviews[0])
        stack.setCustomSpacing(stack.bounds.width / 20, after: stack.arrangedSubviews[1])
    }