Swift - UIStackView - hide if height of all items is below threshold

746 Views Asked by At

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).

1

There are 1 best solutions below

0
DonMag On BEST ANSWER

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
    }

}