UIStackView未遵循代码添加按钮的尺寸,该如何解决?
UIStackView自定义按钮尺寸与圆角问题
我在UIStackView中放置了3个或4个自定义UIButton,需求是:所有按钮为完全圆角,左右两侧的按钮比中间的一个或两个稍大。但UIStackView始终不遵循DeckPageButton设置的bounds。
现有代码
视图控制器中渲染按钮的函数
private func setupButtons() { guard let deck = self.deck else { log.error("Failed to load deck") return } let nFloat = CGFloat(deck.type.buttons.count) let rad = (deckButtonsStackView.bounds.size.width - (8.0 * (nFloat - 1.0))) / nFloat for type in deck.type.buttons { let button = DeckPageButton() button.setupView(type: type, size: CGSize(width: rad, height: rad)) button.translatesAutoresizingMaskIntoConstraints = false deckButtonsStackView.addArrangedSubview(button) } }
DeckPageButton的setupView函数
func setupView(type: DeckPageButtonType, size: CGSize) { bounds.size = size.resized(multiplier: type.radiusCoefficient) layer.cornerRadius = size.height / 2 layer.borderWidth = 5 layer.borderColor = type.borderColor }
遇到的问题
尝试过DeckButtonsStackView的不同Alignment和Distribution选项,均无法解决以下两个问题:
- 按钮无法保持正方形,因此无法呈现完美圆形
- 所有按钮尺寸一致,中间按钮设置更小的bounds也无效
尝试的效果
- 使用
FillEqually时效果相对最好:- 3个按钮:三个圆角按钮尺寸一致,非正方形
- 4个按钮:四个圆角按钮尺寸一致,非正方形
- 尝试设置按钮frame而非bounds:
- 3个按钮:按钮大小混乱,布局错位
- 4个按钮:按钮大小混乱,布局错位
疑问
UIStackView是否不适合我的需求?如果是,有什么更好的方案?
解决方案
UIStackView基于Auto Layout管理子视图,直接修改bounds/frame会被Auto Layout覆盖,因此需要通过约束来控制按钮尺寸,而非手动修改视图边界。
1. 修改DeckPageButton的setupView方法
通过Auto Layout约束保证按钮为正方形,并根据系数设置尺寸:
func setupView(type: DeckPageButtonType, baseSize: CGFloat) { // 根据系数计算最终尺寸 let finalSize = baseSize * type.radiusCoefficient // 添加宽高约束,保证正方形 widthAnchor.constraint(equalToConstant: finalSize).isActive = true heightAnchor.constraint(equalTo: widthAnchor).isActive = true // 设置圆角与边框 layer.cornerRadius = finalSize / 2 layer.borderWidth = 5 layer.borderColor = type.borderColor }
2. 重新计算按钮的基准尺寸
不再均分StackView宽度,而是根据按钮的系数总和计算基准尺寸,保证左右按钮更大:
private func setupButtons() { guard let deck = self.deck else { log.error("Failed to load deck") return } let buttons = deck.type.buttons // 计算所有按钮的系数总和 let totalCoefficient = buttons.reduce(0.0) { $0 + $1.radiusCoefficient } // 计算总间距(8pt间距 * 按钮间隔数) let totalSpacing = 8.0 * CGFloat(buttons.count - 1) // 可用宽度 = StackView宽度 - 总间距 let availableWidth = deckButtonsStackView.bounds.width - totalSpacing // 基准尺寸 = 可用宽度 / 总系数 let baseSize = availableWidth / totalCoefficient for type in buttons { let button = DeckPageButton() button.setupView(type: type, baseSize: baseSize) button.translatesAutoresizingMaskIntoConstraints = false deckButtonsStackView.addArrangedSubview(button) } // 设置StackView的间距为8pt deckButtonsStackView.spacing = 8 }
3. StackView的配置
将StackView的Distribution设为Fill,Alignment设为Center,这样按钮会按照自身约束的尺寸排列,并在垂直方向居中。
4. 补充说明
如果StackView的宽度是动态变化的(比如适配不同屏幕),不要在viewDidLoad中调用setupButtons,应该放在viewDidLayoutSubviews里,此时StackView的bounds才会确定。
替代方案
如果不想用Auto Layout,也可以自定义父视图代替StackView,手动计算每个按钮的frame并添加到父视图,这样完全可控,但需要自行处理屏幕旋转等布局变化场景。
内容的提问来源于stack exchange,提问作者cameloper
相关产品推荐
相关产品推荐

