如何移除数组元素直至WrappingHStack高度不超可用屏幕高度?
问题与解决方案
问题场景
使用WrappingHStack展示自适应宽高的按钮,要求所有元素不溢出屏幕且无滚动,需自动移除足够元素避免垂直溢出。通过GeometryReader获取可用高度,用onChange检测容器高度是否超过可用高度,若超过则随机删除数组元素;同时监听数组元素数量变化再次校验,但Xcode报错:onChange(of: Int) action tried to update multiple times per frame。
报错原因
- 状态更新循环:删除元素后,
buttonContent.count变化触发第二个onChange,该回调又可能再次删除元素,导致同一帧内多次修改状态变量,触发SwiftUI的帧内多次更新检测机制报错。 - 状态变量声明错误:原代码中
buttonContent是普通var而非@State,修改它无法触发视图刷新,逻辑完全失效。
可行解决方案
核心修改点
- 将
buttonContent改为@State变量,确保修改能触发视图重新渲染。 - 避免帧内多次触发状态修改,把删除操作放到下一帧执行,打断循环更新。
- 简化布局结构,优化可用高度的获取方式。
修改后的代码
import SwiftUI import WrappingHStack struct ButtonFitView: View { @State private var availableHeight: CGFloat = 0.0 @State private var buttonContent = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta"] var body: some View { GeometryReader { fullScreenProxy in VStack(spacing: 0) { WrappingHStack(buttonContent, id: \.self, alignment: .center) { text in Button(action: {}) { Text(text) .padding(.horizontal, 12) .padding(.vertical, 8) .background(RoundedRectangle(cornerRadius: 6).fill(.yellow)) } } .background( GeometryReader { contentProxy in Color.clear .onChange(of: contentProxy.size.height) { newHeight in guard newHeight > availableHeight else { return } // 异步执行删除,避免帧内多次更新 DispatchQueue.main.async { removeRandomElement() } } } ) Spacer() } .onAppear { availableHeight = fullScreenProxy.size.height } } .edgesIgnoringSafeArea(.all) } private func removeRandomElement() { guard !buttonContent.isEmpty else { return } buttonContent.remove(at: buttonContent.indices.randomElement()!) } }
关键优化说明
- 状态变量修正:
buttonContent改为@State,确保修改后视图能重新渲染,让高度检测逻辑生效。 - 异步执行删除:用
DispatchQueue.main.async把删除操作放到下一帧,避免同一帧内多次触发onChange回调,打断循环更新。 - 简化布局:直接用外层
GeometryReader获取屏幕可用高度,去掉冗余的ZStack布局,代码更简洁高效。 - 自动循环校验:删除元素后视图重新渲染,
WrappingHStack的高度会再次被检测,若仍溢出则继续删除,直到内容高度符合要求。
内容的提问来源于stack exchange,提问作者Joseph
相关产品推荐
相关产品推荐

