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

SwiftUI监听scenePhase时onChange报错:无法接受双参数闭包

SwiftUI监听scenePhase时onChange编译失败的问题解决

问题重现

代码示例:

struct MyView: View {
    @Environment(\.scenePhase) var scenePhase
    @State private var timerStatus: TimerStatus

    var body: some View {
        VStack {
            Text(...)
            Button(...)
        }
        .onChange(of: scenePhase) { oldState, newState in // 报错行
            if newState == .active {
                // some code
            } else if oldState == .active {
                // some code
            }
        }
        .onChange(of: timerStatus) { oldState, newState in
            // some code
        }
    }
}

enum TimerStatus {
    case paused
    case runningActive
    case runningInactive
}

报错信息:

"Contextual closure type '(ScenePhase) -> Void' expects 1 argument, but 2 were used in closure body"

开发环境:iOS 18.2 + Xcode,跳转至scenePhase对应的onChange定义显示旧版本:

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension View {
    @inlinable nonisolated public func onChange<V>(of value: V, perform action: @escaping (_ newValue: V) -> Void) -> some View where V : Equatable
}

而监听自定义TimerStatus枚举的onChange显示新版本:

@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
extension View {
    nonisolated public func onChange<V>(of value: V, initial: Bool = false, _ action: @escaping (_ oldValue: V, _ newValue: V) -> Void) -> some View where V : Equatable
}

错误原因

核心原因是**@Environment(.scenePhase)的调用没有匹配到iOS 17+推出的双参数onChange重载**。虽然ScenePhase本身遵循Equatable协议,但环境值的传递机制导致编译器优先匹配了iOS 14就存在的单参数版本(仅接收newValue),而自定义@State变量因为是本地可绑定的Equatable类型,成功匹配了新版本的双参数重载。

解决方法

方案1:使用旧版单参数onChange,手动保存旧状态

自己维护一个@State变量记录scenePhase的旧值,实现新旧状态的判断:

struct MyView: View {
    @Environment(\.scenePhase) var scenePhase
    @State private var lastScenePhase: ScenePhase = .active
    @State private var timerStatus: TimerStatus

    var body: some View {
        VStack {
            Text(...)
            Button(...)
        }
        .onChange(of: scenePhase) { newState in
            if newState == .active {
                // some code
            } else if lastScenePhase == .active {
                // some code
            }
            // 更新旧状态记录
            lastScenePhase = newState
        }
        .onChange(of: timerStatus) { oldState, newState in
            // some code
        }
    }
}

方案2:将scenePhase转为本地@State变量,适配新版双参数onChange

先把环境值同步到本地@State变量,再监听这个变量的变化,就能匹配新版双参数重载:

struct MyView: View {
    @Environment(\.scenePhase) var scenePhase
    @State private var localScenePhase: ScenePhase = .active
    @State private var timerStatus: TimerStatus

    var body: some View {
        VStack {
            Text(...)
            Button(...)
        }
        .onAppear {
            localScenePhase = scenePhase
        }
        .onChange(of: scenePhase) { newState in
            localScenePhase = newState
        }
        .onChange(of: localScenePhase) { oldState, newState in
            if newState == .active {
                // some code
            } else if oldState == .active {
                // some code
            }
        }
        .onChange(of: timerStatus) { oldState, newState in
            // some code
        }
    }
}

方案3:显式调用新版onChange重载(iOS 17+专属)

如果项目只需要兼容iOS 17及以上版本,可以通过显式指定initial参数来强制匹配新版重载:

.onChange(of: scenePhase, initial: false) { oldState, newState in
    if newState == .active {
        // some code
    } else if oldState == .active {
        // some code
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:40:10