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

SwiftUI中能否延迟计算视图移除过渡所用变量的值?

解决方案:让父视图使用移除时的点击位置作为过渡锚点

你需要确保移除过渡动画使用的是点击发生时的最新位置,而非视图初始渲染时的位置,这完全可行。以下是两种符合你需求的实现方式,均由父视图控制移除动画:

方案一:通过Binding同步点击位置,确保动画使用最新值

  1. 父视图中定义@State变量存储点击位置,并以Binding形式传递给RainbowView;
  2. 在RainbowView的点击事件中,先更新点击位置,再切换视图状态,并将操作包裹在withAnimation中,保证动画触发时位置已更新;
  3. 父视图的过渡动画直接使用该位置作为锚点,SwiftUI会读取动画启动瞬间的最新状态值,而非视图初始化时的固定值。

修改后的核心代码:

// 父视图
struct ParentView: View {
    @ObservedObject var settings: Settings
    @State private var tapLocation: UnitPoint = .center
    
    var body: some View {
        ZStack {
            if settings.showRainbow {
                RainbowView(tapLocation: $tapLocation, settings: settings)
                    .transition(AnyTransition.asymmetric(
                        insertion: .opacity,
                        removal: .scale(scale: 24, anchor: tapLocation)
                    ))
            } else {
                OtherView()
            }
        }
    }
}

// RainbowView
struct RainbowView: View {
    @Binding var tapLocation: UnitPoint
    @ObservedObject var settings: Settings
    
    var body: some View {
        HStack {
            Color.red.onTapGesture { updateTapLocationAndDismiss(location: .leading) }
            Color.green.onTapGesture { updateTapLocationAndDismiss(location: .center) }
            Color.blue.onTapGesture { updateTapLocationAndDismiss(location: .trailing) }
        }
    }
    
    private func updateTapLocationAndDismiss(location: UnitPoint) {
        withAnimation {
            tapLocation = location
            settings.showRainbow = false
        }
    }
}

class Settings: ObservableObject {
    @Published var showRainbow = true
}

方案二:使用PreferenceKey传递点击位置(复杂视图场景更灵活)

如果RainbowView结构复杂,不想直接传递Binding,可以通过PreferenceKey实现跨视图的位置传递:

  1. 定义偏好键用于存储点击位置:
struct TapLocationPreferenceKey: PreferenceKey {
    static var defaultValue: UnitPoint = .center
    static func reduce(value: inout UnitPoint, nextValue: () -> UnitPoint) {
        value = nextValue()
    }
}
  1. 在RainbowView的点击事件中发送最新位置:
struct RainbowView: View {
    @ObservedObject var settings: Settings
    
    var body: some View {
        HStack {
            Color.red.onTapGesture {
                sendTapLocation(.leading)
                dismiss()
            }
            Color.green.onTapGesture {
                sendTapLocation(.center)
                dismiss()
            }
            Color.blue.onTapGesture {
                sendTapLocation(.trailing)
                dismiss()
            }
        }
    }
    
    private func sendTapLocation(_ location: UnitPoint) {
        self.preference(key: TapLocationPreferenceKey.self, value: location)
    }
    
    private func dismiss() {
        withAnimation {
            settings.showRainbow = false
        }
    }
}
  1. 父视图接收并更新位置:
struct ParentView: View {
    @ObservedObject var settings: Settings
    @State private var tapLocation: UnitPoint = .center
    
    var body: some View {
        ZStack {
            if settings.showRainbow {
                RainbowView(settings: settings)
                    .transition(AnyTransition.asymmetric(
                        insertion: .opacity,
                        removal: .scale(scale: 24, anchor: tapLocation)
                    ))
                    .onPreferenceChange(TapLocationPreferenceKey.self) { newLocation in
                        tapLocation = newLocation
                    }
            } else {
                OtherView()
            }
        }
    }
}

关键注意点

  • 必须用withAnimation包裹状态变化,否则过渡动画无法同步状态更新;
  • 两种方案的核心都是保证移除动画触发前,点击位置已更新为最新值,SwiftUI会自动使用该值作为动画参数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 20:34:54