You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

关键修改说明

  1. 拆分内容视图:把原始文本内容拆成contentView,确保GeometryReader读取的是未被动态高度约束的自然尺寸。
  2. 新增固有高度变量:用intrinsicHeight存储视图的自然高度,作为后续计算的基准,避免依赖被动态约束后的高度。
  3. 修正初始值:将viewHeight初始值设为最低要求的130,避免一开始就出现无限大的情况。
  4. 调整GeometryReader位置:放在overlay中,既可以读取视图的全局位置,又不会影响视图的固有尺寸计算。

如果用ViewModel实现,逻辑完全一致:把intrinsicHeight和viewHeight改成@Published变量,在视图中先获取固有高度再传递给ViewModel计算即可。

内容的提问来源于stack exchange,提问作者Wazza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 20:06:24