SwiftUI中@State变量通过GeometryReader赋值后未更新问题排查
问题分析与解决方案
问题根源
你的viewHeight初始值设为.infinity,同时又用frame(maxHeight: viewHeight)约束视图高度——这直接导致GeometryReader读取到的viewRect.height也是.infinity(因为视图此时的高度被无限大约束)。不管后续怎么计算,最终赋值给viewHeight的还是无限大数值,didSet自然一直输出.inf。另外,把GeometryReader放在background里,它的尺寸完全跟随被约束后的视图,进一步加剧了这个循环依赖问题。
解决方案
核心思路是先获取视图的固有高度(即不被动态高度约束时的自然高度),再基于这个高度计算动态值,同时调整GeometryReader的位置避免循环。
修改后的代码示例:
struct MatchView: View { public let match: Match private let screenWidth = UIScreen.main.bounds.width private var cutShape: some Shape { CutCorners(cutSize: 8, corners: [.topLeading, .topTrailing, .bottomLeading, .bottomTrailing]) } let isLastMatch: Bool // 初始值设为最低要求的130,避免一开始就是无限大 @State private var viewHeight: CGFloat = 130 { didSet { print(viewHeight) } } // 新增变量存储视图的固有高度 @State private var intrinsicHeight: CGFloat = 130 private var frameWidthSize: CGFloat { device.isPhone ? 250 : 300 } var body: some View { // 把原始内容封装成子视图,单独获取固有高度 contentView .clipShape(cutShape) .overlay(cutShape.stroke(Color.Riot.Grey500, lineWidth: 1)) .frame(width: frameWidthSize) // 用计算后的viewHeight固定高度 .frame(height: viewHeight) // 将GeometryReader放在overlay里,读取子视图的全局位置但不影响其固有尺寸 .overlay( GeometryReader { gp -> Color in if isLastMatch { // 首次加载时保存视图的固有高度 if intrinsicHeight == 130 { intrinsicHeight = gp.size.height } self.setViewHeight(viewRect: gp.frame(in: .global)) } return Color.clear } ) .animation(.default, value: viewHeight) } // 提取原始内容为独立子视图 private var contentView: some View { VStack(alignment: .leading, spacing: zeroSpacing) { Text(match.teams[0].name) } } private func setViewHeight(viewRect: CGRect) { let viewX = viewRect.minX // 用固有高度作为计算基准,打破循环依赖 let baseHeight = intrinsicHeight if viewX > 0, viewX < screenWidth { let viewXAsPercent = viewX / screenWidth let newHeight = viewXAsPercent * baseHeight // 确保高度不低于最低要求的130 viewHeight = max(newHeight, 130) } else { // 避免计算后高度低于最小值 viewHeight = max(baseHeight - 130, 130) } } }
关键修改说明
- 拆分内容视图:把原始文本内容拆成
contentView,确保GeometryReader读取的是未被动态高度约束的自然尺寸。 - 新增固有高度变量:用
intrinsicHeight存储视图的自然高度,作为后续计算的基准,避免依赖被动态约束后的高度。 - 修正初始值:将
viewHeight初始值设为最低要求的130,避免一开始就出现无限大的情况。 - 调整GeometryReader位置:放在
overlay中,既可以读取视图的全局位置,又不会影响视图的固有尺寸计算。
如果用ViewModel实现,逻辑完全一致:把intrinsicHeight和viewHeight改成@Published变量,在视图中先获取固有高度再传递给ViewModel计算即可。
内容的提问来源于stack exchange,提问作者Wazza
相关产品推荐
相关产品推荐

