1

I'm creating a simple "toolbar" component with a horizontal axis UIStackView. It looks fine, except when I switch on isLayoutMarginsRelativeArrangement, a strange margin is added to the top above the items, making the height of the stack view incorrect.

I've tried giving the stack view directionalLayoutMargins property many different values, including no value at all. Yet still this unwanted spacing remains. Why does this margin exist and how can I remove it?

override func viewDidLoad() {
    super.viewDidLoad()

    self.stackView = UIStackView(frame: CGRect.zero)
    self.stackView.axis = .horizontal
    self.stackView.alignment = .center
    self.stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
    self.stackView.isLayoutMarginsRelativeArrangement = true
    self.stackView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(self.stackView)

        NSLayoutConstraint.activate([
            self.stackView.topAnchor.constraint(equalTo: view.topAnchor),
            self.stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            self.stackView.widthAnchor.constraint(equalTo: view.widthAnchor),
            self.stackView.heightAnchor.constraint(equalTo: view.heightAnchor)
        ])

        let title = UILabel(frame: .zero)
        title.text = "My Toolbar"
        self.stackView.addArrangedSubview(title)
        title.sizeToFit()

        let button = MDCButton()
        button.setTitle("Recipes", for: .normal)
        button.applyContainedTheme(withScheme: containerScheme)
        button.minimumSize = CGSize(width: 64, height: 48)
        self.stackView.addArrangedSubview(button)

}

enter image description here

zakdances
  • 22,285
  • 32
  • 102
  • 173
  • Please post your code showing how and what you are adding the stackView to. This is possibly a `safeArea` issue. – elliott-io May 06 '20 at 05:42
  • @elliott-io Ok, I added the code. – zakdances May 06 '20 at 11:05
  • @zakdances - why are you setting `.directionalLayoutMargins` to the default (0,0,0,0), and then setting `.isLayoutMarginsRelativeArrangement = true` to begin with? – DonMag May 06 '20 at 14:50
  • Where are you calling the above code from and what exactly is your `view`? It looks like your view is too large for your stackView or there are is a safeArea. Please post that code. – elliott-io May 06 '20 at 19:10
  • @DonMag I'm setting that just to show that it's there isn't a top layout margin causing the issue. – zakdances May 06 '20 at 20:39
  • @zakdances - I'm not seeing the same issue you are. It may be due to your use of `MDCButton`? Try changing that to a normal `UIButton` (you'll have to comment-out the `.applyContainedTheme()` and `minimumSize` lines), and see if you get the same result. – DonMag May 06 '20 at 20:59
  • @elliott-io calling it in `viewDidLoad`. Added that to the example. – zakdances May 06 '20 at 21:17
  • @DonMag I've tried taking out the button entirely and leaving only the "My Toolbar" UILabel. Same issue. – zakdances May 06 '20 at 21:18
  • Thanks. How is your class declared? What kind of View and/or Controller is this? – elliott-io May 06 '20 at 21:23
  • @elliott-io it's a `UIViewController` subclass which is added as a child to another view controller. It's view is added to a `UIStackView`. I'm actually now thinking that it's a content hugging issue, but so far `self.view.setContentHuggingPriority(.defaultHigh, for: .vertical)` doesn't produce any change – zakdances May 06 '20 at 21:35
  • 2
    @elliott-io I just set `insetsLayoutMarginsFromSafeArea` on the stack view to `false` and now it's working. So I guess you were right about the `safeArea`. Not sure what's wrong with my safe area... – zakdances May 07 '20 at 02:52
  • @zakdances, great! Nothing is wrong with it. iOS sets it automatically to avoid UI elements going under headers and whatnot at the edge of the screen. Your screenshot looked like it was about the area of a safeArea's `top` spacing. – elliott-io May 07 '20 at 03:06
  • Be careful when ignoring safe area's. :-) They vary by device... – elliott-io May 07 '20 at 03:08
  • @elliott-io Thank you, I hear you - but this particular view is nowhere near the top or bottom of the screen. It's right in the middle. Which makes it's internal safe area that much more vexing. – zakdances May 07 '20 at 04:48
  • Perfect, wanted to give a heads up just in case. Happy coding! – elliott-io May 07 '20 at 05:12

2 Answers2

0

Here's a couple things to try...

First, StackBarViewControllerA which creates a newView (plain UIView) to hold the stack view with the label and button:

class StackBarViewControllerA: UIViewController {

    var stackView: UIStackView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let newView = UIView()
        newView.translatesAutoresizingMaskIntoConstraints = false
        newView.backgroundColor = .cyan
        view.addSubview(newView)

        self.stackView = UIStackView(frame: CGRect.zero)
        self.stackView.axis = .horizontal
        self.stackView.alignment = .center
        self.stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
        self.stackView.isLayoutMarginsRelativeArrangement = true
        self.stackView.translatesAutoresizingMaskIntoConstraints = false
        newView.addSubview(self.stackView)

        let g = view.safeAreaLayoutGuide

        NSLayoutConstraint.activate([

            self.stackView.topAnchor.constraint(equalTo: newView.topAnchor),
            self.stackView.bottomAnchor.constraint(equalTo: newView.bottomAnchor),
            self.stackView.widthAnchor.constraint(equalTo: newView.widthAnchor),
            self.stackView.heightAnchor.constraint(equalTo: newView.heightAnchor),

            newView.topAnchor.constraint(equalTo: g.topAnchor),
            newView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            newView.trailingAnchor.constraint(equalTo: g.trailingAnchor),

        ])

        let title = UILabel(frame: .zero)
        title.text = "My Toolbar"
        self.stackView.addArrangedSubview(title)
        title.sizeToFit()

        let button = UIButton() // MDCButton()
        button.setTitle("Recipes", for: .normal)
        button.backgroundColor = .blue
        button.heightAnchor.constraint(equalToConstant: 48).isActive = true
        //button.applyContainedTheme(withScheme: containerScheme)
        //button.minimumSize = CGSize(width: 64, height: 48)
        self.stackView.addArrangedSubview(button)
    }

}

Second, StackBarViewControllerB using a custom StackBarView:

class StackBarView: UIView {

    var stackView: UIStackView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    func commonInit() -> Void {

        self.stackView = UIStackView(frame: CGRect.zero)
        self.stackView.axis = .horizontal
        self.stackView.alignment = .center
        self.stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
        self.stackView.isLayoutMarginsRelativeArrangement = true
        self.stackView.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(self.stackView)

        NSLayoutConstraint.activate([
            self.stackView.topAnchor.constraint(equalTo: self.topAnchor),
            self.stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            self.stackView.widthAnchor.constraint(equalTo: self.widthAnchor),
            self.stackView.heightAnchor.constraint(equalTo: self.heightAnchor)
        ])

        let title = UILabel(frame: .zero)
        title.text = "My Toolbar"
        self.stackView.addArrangedSubview(title)
        title.sizeToFit()

        let button = UIButton() // MDCButton()
        button.setTitle("Recipes", for: .normal)
        button.backgroundColor = .blue
        //button.applyContainedTheme(withScheme: containerScheme)
        //button.minimumSize = CGSize(width: 64, height: 48)
        button.widthAnchor.constraint(greaterThanOrEqualToConstant: 64).isActive = true
        button.heightAnchor.constraint(greaterThanOrEqualToConstant: 48).isActive = true
        button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        self.stackView.addArrangedSubview(button)

    }

}

class StackBarViewControllerB: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let v = StackBarView()

        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .cyan

        view.addSubview(v)

        let g = view.safeAreaLayoutGuide

        NSLayoutConstraint.activate([

            v.topAnchor.constraint(equalTo: g.topAnchor),
            v.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            v.trailingAnchor.constraint(equalTo: g.trailingAnchor),

        ])

    }

}

Both give this result (I gave it a cyan background so we can see the frame):

enter image description here

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
0

Looks like the problem is your anchor settings. Try removing your bottomAnchor and set your heightAnchor to the size you want for your buttons plus some padding, instead of just matching the heightAnchor of view:

NSLayoutConstraint.activate([
    self.stackView.topAnchor.constraint(equalTo: view.topAnchor),
    // self.stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    self.stackView.widthAnchor.constraint(equalTo: view.widthAnchor),
    // self.stackView.heightAnchor.constraint(equalTo: view.heightAnchor),
    self.stackView.heightAnchor.constraint(equalToConstant: 80)
])

Note: You may need to set an offset constant for your top anchor like: self.stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50)

elliott-io
  • 1,394
  • 6
  • 9
  • Sure. But something needs to have a fixed size, or they need to all be relative to one another. If your childButton has a fixed height, set your `stackView.heightAnchor` to match `childButton.heightAnchor`. Make sure you call `childButton.setNeedsLayout` before adding the stackView constraints. – elliott-io May 07 '20 at 02:34
  • Obviously you would need to create your child button and add it to stackView first before forcing a layout update as above comment. – elliott-io May 07 '20 at 02:37