-2

I am trying to use UIStackView with dynamic content on my project. The stackview is pinned to the leading & trailing of the container view. Why have I pinned it? Because Xcode will otherwise complained that I have to specify width or X position. But maybe this is not the right approach - let me know. The stack view is currently configured with:

  • alignment: center (i've tried 'Filled' too)
  • distribution: equal spacing

Anyhow, the content of the stackview itself is created from a XIB file, which gets expanded into a custom UIView. This kind of works, but I'm having several issues with the layout:

  1. When there are only a few items in the stackview, then they are sparsely distributed. Ideally I want the UIView (the orange button with the green area) to resize as big as possible to fill in these gaps issue 1 - lots of gaps between uiview

  2. When there are a lot of items in the stackview, they are currently stacked on top of each other. This is an issue. The individual UIView should be resized to the biggest size that will make them fit horizontally in the UIStackview. issue 2 - not enough space between uiviews

What I've done to add the UIView(s) to the stackview is as follow:

labelStackView.arrangedSubviews.forEach { (view) in
 view.removeFromSuperview()
}

for (index, character) in model.modelName.enumerated() {
     // CharacterPlaceholder extends UIView
     let characterPlaceHolder = CharacterPlaceholder()
     ...
     labelStackView.addArrangedSubview(characterPlaceHolder)
}                   

while CharacterPlaceholder roughly looks like below

class CharacterPlaceholder: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        customInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        customInit()
    }

    private func customInit(){           
        let nib = UINib(nibName: "CharacterPlaceholder", bundle: nil)
       
               if let view = nib.instantiate(withOwner: self, options: nil).first as? UIView {
                   addSubview(view)
                   
                   view.frame = self.bounds
               }
    }

I have uploaded the project to my github account: https://github.com/alexwibowo/Flipcard

Any idea what I've done wrong? Should I grab the screensize at the start (or during screen rotation), and then manually calculate the width required for the buttons? That seems painful & very manual. I'm hoping that there is some autolayout magic that I can use here.

Thank you in advance for the help!

Edit: I gather that I need to use 'fill equally' for the distribution, so that the individual letter blocks will have the same size. But also, so that they are constrained to the available stackview space. However, now they can still overlap.

caffeine_inquisitor
  • 617
  • 1
  • 9
  • 21
  • The answer you posted *may* solve your issue, but by using calculations like that you are throwing away a lot of what auto-layout can do for you. Do you want the letter-blocks in the green view to "align left" like you show on the top? Or do you want them to size to fill the green view width? Do you want them to remain square, or stretch to wide rectangles? – DonMag Aug 17 '20 at 15:35

1 Answers1

0

Ok.. I've figured a way to do this. First, as I've mentioned, I need to set the distribution to 'fill equally' so that each view in the stackview will be sized equally. But doing that by itself is not enough. I need to get the screen size. i.e. through

view.frame.width

Then, when I expand the xib file, i need to resize it manually using simple maths. E.g.:

let viewWidth = view.frame.width
let numberOfCharacters = model.modelName.count
let widthOfEach = Int(viewWidth) / numberOfCharacters

then the resizing part:

characterPlaceHolder.widthConstraint.constant = widthOfEachIncludingMargin

That widthConstraint is an outlet that I've set on the characterPlaceHolder view.

caffeine_inquisitor
  • 617
  • 1
  • 9
  • 21