0

I have UIStackView in vertical mode filled with UIButtons. I have dynamic screen resize and if all buttons in stack view have height under some threshold, I want to hide them. How to achive this automatically?

I have tried to extend UIButton and add:

override func layoutSubviews() {
    super.layoutSubviews()
    self.isHidden = (self.frame.height < 20)
}

which works, but once the button is hidden it will never re-appear and layoutSubviews is never called back (even if the height should be again larger).

Martin Perry
  • 9,232
  • 8
  • 46
  • 114
  • Is your stack view set to `distribution = .fillEqually`, so all the buttons are the same height? Do you need to set `.isHidden`, or would setting `.alpha = 0.0` / `.alpha = 1.0` be a suitable option? (Buttons with `.alpha = 0.0` do **NOT** accept touches.) – DonMag Sep 13 '21 at 16:59
  • @DonMag Yes, all buttons are the same height. Set `alpha` to zero is problematic, since the buttons are transparent based on some other information which have to be recreated when they "appear" again. However, if it is the only option, it can be done. – Martin Perry Sep 13 '21 at 18:20

1 Answers1

1

It's not clear what all you are doing, or why you say it would be problematic to set the buttons' .alpha property, but here are two approaches, both using a UIStackView subclass and handling the show/hide in layoutSubviews().

1: calculate what the button heights will be and set .isHidden property:

class MyStackView: UIStackView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        axis = .vertical
        distribution = .fillEqually
        spacing = 8
    }
    override func layoutSubviews() {
        super.layoutSubviews()

        // approach 1
        //  setting .isHidden
        let numViews = arrangedSubviews.count
        let numSpaces = numViews - 1
        let h = (bounds.height - (spacing * CGFloat(numSpaces))) / CGFloat(numViews)
        let bHide = h < 20
        arrangedSubviews.forEach { v in
            v.isHidden = bHide
        }
        
    }
    
}
  1. set .isHidden property based on what the button heights are (much simpler):

    class MyStackView: UIStackView {

     override init(frame: CGRect) {
         super.init(frame: frame)
         commonInit()
     }
     required init(coder: NSCoder) {
         super.init(coder: coder)
         commonInit()
     }
     func commonInit() -> Void {
         axis = .vertical
         distribution = .fillEqually
         spacing = 8
     }
     override func layoutSubviews() {
         super.layoutSubviews()
    
         // approach 2
         //  setting .alpha
         arrangedSubviews.forEach { v in
             v.alpha = v.frame.height < 20 ? 0.0 : 1.0
         }
    
     }
    

    }

And here's a sample controller to see it in use. Tapping anywhere will toggle the height of the stack view between 300 and 100 (buttons will have less-than 20-pts height at 100):

class ConditionalStackViewController: UIViewController {
    
    let stackView: MyStackView = {
        let v = MyStackView()
        // so we can see the stack view frame
        v.backgroundColor = .systemYellow
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    var stackHeight: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        for i in 1...6 {
            let b = UIButton()
            b.setTitle("Button \(i)", for: [])
            b.setTitleColor(.white, for: .normal)
            b.setTitleColor(.lightGray, for: .highlighted)
            b.backgroundColor = .red
            stackView.addArrangedSubview(b)
        }
        
        view.addSubview(stackView)
        
        let g = view.safeAreaLayoutGuide
        
        stackHeight = stackView.heightAnchor.constraint(equalToConstant: 300.0)
        
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            stackHeight,
        ])
        
        let t = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
        view.addGestureRecognizer(t)
    }
    
    @objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
        stackHeight.constant = stackHeight.constant == 300 ? 100 : 300
    }

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