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

修改Popover内容出现异常重载滚动动画而非平滑过渡

解决SwiftUI Popover内Menu修改状态导致的异常滚动动画问题

开发Popover时,需要根据上方选项动态展开内容,期望实现无动画或平滑展开效果,但实际选择Menu内选项后,Popover出现类似嵌套滚动视图的菜单重载动画。奇怪的是用普通Button修改状态时不会触发这个问题,尝试添加.id()修饰符、通过opacity/frame控制内容显隐都无法解决。

演示动画

原代码如下:

struct DropdownTestView: View {
    @State var presentPopover: Bool = false
    @State var choice: Int = 0

    var body: some View {
        VStack {
            Button {
                presentPopover = true
            } label: {
                Text("Press To Choose")
            }
            .popover(isPresented: $presentPopover) {
                popoverView
                    .padding()
            }
            Spacer()
        }
        .padding()
    }

    var popoverView: some View {
        VStack(alignment: .leading) {
            Divider()

            Menu {
                ForEach(0..<6) { num in
                    Button {
                        choice = num
                    } label: {
                        Text("\(num)")
                    }
                }
            } label: {
                Text("Make A Choice")
            }

            if choice != 0 {
                Divider()
                HStack {
                    Text("WRONG CHOICE")
                }
            }
        }
        .frame(width: 220)
    }
}

之前尝试过的无效写法:

HStack {
    Text("WRONG CHOICE")
}
.opacity(choice != 0 ? 1.0 : 0)
.frame(height: choice != 0 ? 30 : 0)

问题根源

Menu组件内部自带动画逻辑,当它修改外部@State变量时,会将自身的动画上下文传递给Popover的视图更新流程,导致整个Popover内容触发不必要的滚动/重载动画,而普通Button不会携带这种额外的动画上下文。

解决方案

方案1:修改状态时禁用动画

在Menu内的Button动作里,用withAnimation(.none)包裹状态修改,切断Menu的动画传递:

Menu {
    ForEach(0..<6) { num in
        Button {
            withAnimation(.none) {
                choice = num
            }
        } label: {
            Text("\(num)")
        }
    }
} label: {
    Text("Make A Choice")
}

方案2:给动态内容禁用事务动画

给需要动态显示的内容添加.transaction修饰符,强制取消动画:

if choice != 0 {
    Divider()
    HStack {
        Text("WRONG CHOICE")
    }
    .transaction { transaction in
        transaction.animation = nil
    }
}

两种方案都能解决问题,选择哪种取决于你的需求:如果希望所有状态更新都不带动画,用方案1;如果只针对动态内容取消动画,用方案2。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 10:58:16