【问题标题】:Not Receiving scenePhase Changes不接收场景相位变化
【发布时间】:2022-05-10 03:25:31
【问题描述】:

我正在尝试执行一些我之前放在我的应用程序委托中的代码,例如在进入后台时保存我的托管对象上下文。我为场景阶段拨打了.onChange 的电话,但我什么也没得到。这是一个示例项目:

import SwiftUI

@main
struct PhaseApp: App {
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some Scene {
        WindowGroup {
            Text("Hello, world.")
        }
        .onChange(of: scenePhase) { phase in
            switch phase {
            case .active:
                print("Active")
            case .background:
                print("Background")
            case .inactive:
                print("Inactive")
            @unknown default: break
            }
        }
    }
}

我希望每当我按下 Home 或点击应用程序时,模拟器或我的测试设备上都会收到打印命令,但没有任何反应。

【问题讨论】:

  • @Asperi 的回答确实有效。但是场景扩展中的文档有一个在 WindowGroup 上调用 onChange 运算符的示例。所以,我认为这是一个错误。
  • 在 iOS 14.7 中似乎仍然如此。 :(
  • Aswath 是对的,这是一个错误。如果您只需要了解各个场景的场景相位,@Asperi 的答案就有效。但是,如果您需要了解整个应用程序的场景相位,那么它是不正确的。

标签: swiftui


【解决方案1】:

使用内部场景根视图(通常是ContentView

使用 Xcode 12 / iOS 14 进行测试。

struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase
    var body: some View {
        TestView()
            .onChange(of: scenePhase) { phase in
                switch phase {
                    case .active:
                        print(">> your code is here on scene become active")
                    case .inactive:
                        print(">> your code is here on scene become inactive")
                    case .background:
                        print(">> your code is here on scene go background")
                    default:
                        print(">> do something else in future")
                }
            }
    }
}

backup

【讨论】:

  • 仅供参考,这个答案有点误导,因为它没有提供与海报代码预期相同的结果。海报的代码会告诉你关于整个应用程序的场景相位变化。 @Asperi 的回答只会告诉你当前场景的场景相位变化。谨慎使用此答案,希望 Apple 修复 WindowGroup 上的错误。
【解决方案2】:

我一直在使用 Xcode 12 beta 3 和 iOS/iPadOS 14 beta 3 进行测试,这就是我的发现。请注意,其中很多涉及支持多个窗口,但“SwiftUI 生命周期”项目 默认 将其打开,因此我怀疑您已经将其激活。在我最初的情况下,我将现有的 SwiftUI 应用程序从 SceneDelegate 移植到使用新的 App 结构,因此我已经启用了多窗口支持。

这是我在新测试应用程序中使用的测试View

struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase

    var body: some View {
        Text("Hello, world!").padding()
            .onChange(of: scenePhase) { phase in
                switch phase {
                case .background:
                    print("PHASECHANGE: View entered background")
                case .active:
                    print("PHASECHANGE: View entered active")
                case .inactive:
                    print("PHASECHANGE: View entered inactive")
                @unknown default:
                    print("PHASECHANGE: View entered unknown phase.")
                }
            }
    }
}

(我在AppScene 中有相同的代码,但它们从不打印任何东西。)

  1. ScenePhase documentation 声称您可以在AppSceneView 内声明onChange。在我可以设计的任何情况下,我都没有看到 AppScene 级别版本执行过,View 级别版本似乎无法完全正确执行。

  2. 支持多个窗口的硬件上(我使用第 7 代 iPod touch)View 级别闭包每次执行一次。 (完全披露,这款 iPod Touch 仍在运行 beta 2,但我认为这并不重要。一旦我将它更新到 b3,我会在此处提及它。) 编辑(确实很重要。) 在运行不支持多个窗口的 beta 2 的硬件(第 7 代 iPod Touch)上,我看到应用程序进入后台,回到前台,等等。在每次应用启动时,我都会看到“查看已进入活动状态”打印。

  3. 确实支持多个窗口的硬件上(我使用带有 Lightning 连接器的旧 iPad Pro)我没有看到初始场景创建发生。 (第一次运行不会触发“查看进入活动状态”消息。)我确实看到了后续的背景/前景转换。如果我从 iPad 多任务 UI 创建一个新场景,second 场景将触发“查看进入活动状态”日志。不幸的是,我没有在 iPad 上针对 beta 2 运行这个测试,所以我不能说行为是否随着 b3 改变。

  4. 在运行 iOS 14 beta 3 的 iPod Touch 上,我看到了与 iPad 相同的行为:第一次启动不会从视图中打印任何相变消息,但会报告后续的背景/前景变化。

  5. 在模拟器上它总是表现得像 iPad 硬件,即使我在模拟 iPod Touch。我怀疑这是因为模拟器在 Mac 上运行并以这种方式获得多窗口“支持”。但是当我在模拟器中运行时将应用程序置于后台时确实会看到消息,我只是错过了从实际硬件获得的初始“查看已进入活动状态”消息。

最后一点:当我从前台返回应用程序时,我首先看到“视图进入非活动状态”,然后我看到“视图进入活动状态”。当我后台应用程序时,我看到“查看进入非活动状态”,然后是“查看进入后台”。我认为这是预期的行为,但由于其他部分似乎已损坏,我想提一下。


TL;DR:

我认为您应该能够大部分看到ScenePhaseView 相比的变化,但您会错过在 iPad 或模拟器中的初始应用启动。希望它们会在以后的 beta 中按预期显示 AppScene 对象?

【讨论】:

    【解决方案3】:

    我承认这个问题是专门关于 schenePhase 更改的,但是,在 macOS 上,当用户切换到其他应用程序时,我无法收到任何 .background 通知。较旧的NotificationCenter 策略在两个平台上都按我的预期工作。我会为任何试图执行一些代码的人添加这个组合,onForeground / onBackground iOSmacOS

    在任何视图上,您​​都可以附加:

    .onReceive(NotificationCenter.default.publisher(for: .willResignActiveNotification)) { _ in
        doBackgroundThing()
    }
    

    您可能关心的事件是:

    你可以找到所有NotificationCenterNameshere

    我使用will* 变体作为背景,因为我假设它们会在流程的早期被调用,我使用did* 变体作为前台,因为无论应用程序是否首次启动,它们都会被调用,或者它是从背景中出来的。

    我使用这个扩展,所以我不必考虑平台差异:

    extension View {
        #if os(iOS)
        func onBackground(_ f: @escaping () -> Void) -> some View {
            self.onReceive(
                NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification),
                perform: { _ in f() }
            )
        }
        
        func onForeground(_ f: @escaping () -> Void) -> some View {
            self.onReceive(
                NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification),
                perform: { _ in f() }
            )
        }
        #else
        func onBackground(_ f: @escaping () -> Void) -> some View {
            self.onReceive(
                NotificationCenter.default.publisher(for: NSApplication.willResignActiveNotification),
                perform: { _ in f() }
            )
        }
        
        func onForeground(_ f: @escaping () -> Void) -> some View {
            self.onReceive(
                NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification),
                perform: { _ in f() }
            )
        }
        #endif
    }
    

    正如所料,我是这样使用它的:

    AppView()
       .onBackground {
           print("my background")
       }
       .onForeground {
           print("my foreground")
       }
    

    【讨论】:

    • 在 macOS 上,.onChange(of:) 没有收到任何 ScenePhase 更改——无论是在场景中还是在视图中。很奇怪,因为每个文档都说它应该这样做。你的回答解决了我的问题。
    • @ChrisR 如果我绑定到我的根视图并点击黄色的- 按钮,我只能接收.inactive。也许当您的系统资源不足时,我们也会看到其他状态?
    【解决方案4】:

    您可以使用以下扩展名:

    public extension View {
        func onScenePhaseChange(phase: ScenePhase, action: @escaping () -> ()) -> some View {
            self.modifier(OnScenePhaseChangeModifier(phase: phase, action: action))
        }
    }
    
    public struct OnScenePhaseChangeModifier: ViewModifier {
        @Environment(\.scenePhase) private var scenePhase
        
        public let phase: ScenePhase
        
        public let action: () -> ()
        
        public func body(content: Content) -> some View {
            content
                .onChange(of: scenePhase) { phase in
                    if (self.phase == phase) {
                        action()
                    }
                }
        }
    }
    

    最终用法:

    ContentView()
         .onScenePhaseChange(phase: .active)     { print("scene activated!") }
         .onScenePhaseChange(phase: .background) { print("scene backgrounded!") }
         .onScenePhaseChange(phase: .inactive)   { print("scene inactive!") }
    

    【讨论】:

    • 如果我这样使用它,所有状态都会被触发!只是按照您在代码中编写它们的顺序。我猜如果 (phase == phase) 可能是问题所在。我重命名了一个,效果更好。
    • code public struct OnScenePhaseChangeModifier: ViewModifier { @Environment(\.scenePhase) private var scenePhase public let phase: ScenePhase public let action: () -> () public func body(content: Content) -> some View { content .onChange(of: scenePhase) { newphase in if (phase == newphase) { action() } } } } code
    【解决方案5】:

    就我而言,我将“@Environment(.scenePhase) private var scenePhase”放在 ContentView 中。然后 onChange 在子视图中起作用。

    【讨论】:

      猜你喜欢
      • 2019-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-12
      • 1970-01-01
      相关资源
      最近更新 更多