2

I'm trying to achieve the following layout (views centered horizontally):

enter image description here

So I set up a stack view like so:

let quickPromptsView: UIStackView = {
    let sv = UIStackView()
    sv.axis = .horizontal
    sv.alignment = .center
    sv.distribution = .equalSpacing
    sv.spacing = 10
    sv.translatesAutoresizingMaskIntoConstraints = false
    return sv
}()

Adding buttons to stack views:

func addOptions(options: [DialogNodeOutputOptionsElement]) {
    DispatchQueue.main.async {
        // clear all subviews
        self.quickPromptsView.subviews.forEach { (view) in
            view.removeFromSuperview()
        }
        for option in options {
            let button = QuickPromptButton()
            button.setTitle(option.label, for: .normal)
            button.addTarget(self, action: #selector(self.didTapQuickPrompt), for: .touchUpInside)
            self.quickPromptsView.addArrangedSubview(button)
        }
    }
}

Button class:

class QuickPromptButton: UIButton {

    var userFacingValue: String?
    var answerValue: String?

    override init(frame: CGRect) {
        super.init(frame: frame)
        layer.borderColor = UIColor.primaryColor.cgColor
        layer.borderWidth = 1
        layer.cornerRadius = 15
        setTitleColor(.primaryColor, for: .normal)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

This is how I add the stack view, I add it inside another stack part of the MessageKit

func configureQuickPromptsView() {
    view.addSubview(quickPromptsView)
    quickPromptsView.heightAnchor.constraint(equalToConstant: 40).isActive = true

    // this stack view belongs to the MessageKit library
    messageInputBar.topStackView.axis = .horizontal
    messageInputBar.topStackView.distribution = .fill
    messageInputBar.topStackView.addArrangedSubview(quickPromptsView)
}

However, this is what I get:

enter image description here

The stack view has 100% the width of the screen. I've tried every single type of distribution but that didn't work. I've also tried to insert transparent UIViews at the extremes to force the centering but that seems like a hack. Any ideas?

koen
  • 5,383
  • 7
  • 50
  • 89
Cesare
  • 9,139
  • 16
  • 78
  • 130

1 Answers1

2

Just center your stack view horizontally in its superview.

Examples below are using UIKitPlus library (it is pure UIKit but declarative and support LivePreview, iOS9+)

UView {
    UHStack {
        UView {
            UText("Yes")
                .color(.green)
                .edgesToSuperview(h: 8, v: 4)
        }
        .border(1, .green)
        .circle()
        UView {
            UText("No")
                .color(.red)
                .edgesToSuperview(h: 8, v: 4)
        }
        .border(1, .red)
        .circle()
        UView {
            UText("I don't know")
                .color(.darkGray)
                .edgesToSuperview(h: 8, v: 4)
        }
        .border(1, .darkGray)
        .circle()
    }
    .alignment(.center)
    .distribution(.equalSpacing)
    .spacing(10)
    .edgesToSuperview(v: 0)
    .centerXInSuperview() // this will center your stack view
}
.edgesToSuperview(h: 0)
.centerYInSuperview()

First solution

Or use a little hack by adding two views with equal width, one in the beginning of stack and one as the last view in stack.

let v1 = UView()
UHStack {
    v1
    UView {
        UText("Yes")
            .color(.green)
            .edgesToSuperview(h: 8, v: 4)
    }
    .border(1, .green)
    .circle()
    UView {
        UText("No")
            .color(.red)
            .edgesToSuperview(h: 8, v: 4)
    }
    .border(1, .red)
    .circle()
    UView {
        UText("I don't know")
            .color(.darkGray)
            .edgesToSuperview(h: 8, v: 4)
    }
    .border(1, .darkGray)
    .circle()
    UView().width(to: v1)
}
.alignment(.center)
.spacing(10)
.edgesToSuperview(h: 0)
.centerYInSuperview()

Second solution

The stack view itself can't center inner views automatically unfortunately, so you have to help it to do that.

imike
  • 5,515
  • 2
  • 37
  • 44
  • 2
    this question for uikit not swiftui – Shehata Gamal Mar 31 '20 at 15:25
  • Thank you for this answer. I don't think centering horizontally is an option here because I'm using a stack view which is built into a library (MessageKit) and don't want to touch the constraints there. Using two views as a hack sounds ok, however, I'm not sure how to go about doing that in UIKit without third party libraries – Cesare Mar 31 '20 at 15:29
  • @Cesare Equal width is a native thing, just set width constraint of first view to width of second view and that's it. Programmatically you can use `widthAnchor` property of UIView to set it :) – imike Mar 31 '20 at 15:39
  • @Sh_Khan this answer is written in pure UIKit and works since iOS9 – imike Mar 31 '20 at 15:39
  • I'll accept and add – if you're using a third-party library such as MessageKit you can add a container view `UIView` to the pre-built stack view which will hold _another_ stack view, but this time you decide the constraints (such as center horizontally). (Not sure if this made sense but here ya go) – Cesare Mar 31 '20 at 15:43
  • I'd recommend you to avoid `MessageKit` usage and write your chat window by yourself since it is very easy. Check UIKitPlus example app. But since you're using it I think that there is nothing bad to add in their container view your UIView with your centered StackView inside :) – imike Mar 31 '20 at 15:49