不接收场景相变

dav*_*eme 14 swiftui

我正在尝试执行一些我之前放在我的应用程序委托中的代码,例如在进入后台时保存我的托管对象上下文。我将调用放入了.onChangeScenePhase,但我什么也没收到。这是一个示例项目:

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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Par*_*tra 31

我承认这个问题专门与更改有关,但是,在 macOS 上,当用户切换到其他应用程序时,schenePhase我无法收到任何通知。.backgroundNotificationCenter策略在两个平台上都按我的预期运行。我会将其添加到任何只是尝试执行某些代码onForeground/onBackgroundiOS的人的组合中macOS

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

.onReceive(NotificationCenter.default.publisher(for: .willResignActiveNotification)) { _ in
    doBackgroundThing()
}
Run Code Online (Sandbox Code Playgroud)

您可能关心的事件有:

您可以在这里找到所有NotificationCenter Name内容。

我使用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
}
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,我这样使用它:

AppView()
   .onBackground {
       print("my background")
   }
   .onForeground {
       print("my foreground")
   }
Run Code Online (Sandbox Code Playgroud)

  • 这太棒了——谢谢! (2认同)

Asp*_*eri 10

使用内部场景根视图(通常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")
                }
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考,这个答案有点误导,因为它没有提供与海报代码预期的结果相同的结果。海报的代码将告诉您有关整个应用程序的 scenePhase 更改。@Asperi的答案只会告诉你当前场景的scenePhase变化。谨慎使用这个答案,希望 Apple 修复 WindowGroup 上的错误。 (4认同)

Tim*_*ers 5

我一直在使用 Xcode 12 beta 3 和 iOS/iPadOS 14 beta 3 进行测试,这就是我的发现。请注意,其中很多涉及支持多个窗口,但“SwiftUI 生命周期”项目默认将其打开,因此我怀疑您已经将其激活。在我最初的情况下,我将现有的 SwiftUI 应用程序从 a 移植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.")
                }
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

(我在App& 中有相同的代码,Scene但它们从不打印任何内容。)

  1. ScenePhase文档声称,你可以声明onChangeApp,一个Scene 一个View。在我可以设计的任何情况下,我都没有看到AppScene级别版本执行,并且View级别版本似乎没有完全正确执行。

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

  3. 在硬件支持多窗口(我用一个旧的iPad临用闪电接口)我没有看到初始场景创建发生。(第一次运行并没有触发“视图进入活跃”的消息。)我确实看到后续的背景/前景的转变。如果我从 iPad 多任务用户界面创建一个新场景,第二个场景将触发“视图输入活动”日志。不幸的是,我没有在 iPad 上针对 beta 2 运行此测试,所以我不能说行为是否随 b3 改变。

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

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

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


特尔;博士:

我认为您应该能够看到 a 的大部分 ScenePhase更改View,但您会错过最初的应用程序启动在 iPad 上或在模拟器中. 并且希望它们会在以后的测试版中按预期显示AppScene对象?