Swift 垂直UIStackView内按钮高度低于阈值时自动隐藏的实现问题
问题根因
UIKit 不会为 isHidden = true 的视图触发 layoutSubviews 回调,视图隐藏后会被排除在正常的布局刷新流程之外,所以即使后续高度回到阈值以上,也没有机会重新执行显示逻辑,这就是你现有方案失效的核心原因。
可行实现方案
推荐优先使用父栈视图统一处理的方案,性能更高、逻辑更集中,也不会出现循环回调问题。
方案1:自定义栈视图统一处理
直接在垂直栈视图的布局回调中批量判断所有子按钮的高度,栈视图的layoutSubviews每次布局变动都会触发,不受子按钮隐藏状态的影响:
class AutoHideVerticalStackView: UIStackView { // 可外部配置的高度阈值 var hideHeightThreshold: CGFloat = 20 override init(frame: CGRect) { super.init(frame: frame) axis = .vertical } required init(coder: NSCoder) { super.init(coder: coder) axis = .vertical } override func layoutSubviews() { super.layoutSubviews() arrangedSubviews.forEach { subview in guard let button = subview as? UIButton else { return } // 栈视图已经计算好所有子视图的frame,不管子视图是否隐藏都可以拿到正确值 subview.isHidden = subview.frame.height < hideHeightThreshold } } }
把你原来的UIStackView替换成这个自定义类即可,不需要修改任何按钮的代码。如果不想自定义栈视图,也可以在持有栈视图的父视图/视图控制器的layoutSubviews方法中写上面的判断逻辑。
方案2:按钮扩展+KVO监听frame变化
如果你不想修改栈视图的逻辑,也可以给UIButton添加KVO监听自身的frame属性,即使按钮隐藏,frame变动的KVO回调也会正常触发:
import UIKit private var thresholdKey: UInt8 = 0 private var kvoObserverKey: UInt8 = 0 extension UIButton { /// 自动隐藏的高度阈值,默认20 var autoHideThreshold: CGFloat { get { objc_getAssociatedObject(self, &thresholdKey) as? CGFloat ?? 20 } set { objc_setAssociatedObject(self, &thresholdKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /// 开启低于阈值自动隐藏功能 func enableAutoHideByHeight() { // 移除旧监听避免重复注册 if let oldObserver = objc_getAssociatedObject(self, &kvoObserverKey) as? NSKeyValueObservation { oldObserver.invalidate() } // 监听frame变化 let observer = observe(\.frame, options: [.new]) { [weak self] _, change in guard let self = self, let newFrame = change.newValue else { return } self.isHidden = newFrame.height < self.autoHideThreshold } objc_setAssociatedObject(self, &kvoObserverKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } }
使用时只要对需要生效的按钮调用一次enableAutoHideByHeight()即可。
内容的提问来源于stack exchange,提问作者Martin Perry
相关产品推荐
相关产品推荐

