3

How can I trigger an action when scenePhase changes in a sheet? I tried onChange modifier but it doesn't trigger when the app goes to the background.

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .sheet(isPresented: .constant(true), content: SheetView.init)
    }
}

struct SheetView: View {
    @Environment (\.scenePhase) private var scenePhase
    @State private var isFolderLocked = true
    var body: some View {
        Text("Hello, world!")
            .onAppear {
                print("view appeared.")
                // if folder is locked, authenticate and unlock it
            }
            .onChange(of: scenePhase) { newValue in
                print("scenePhase changed.")
                // if scenePhase == .background, lock folder
            }
    }
}

Updated code:

import SwiftUI

struct ContentView: View {
    @Environment (\.scenePhase) private var scenePhase
    var body: some View {
        Text("Hello, world!")
            .sheet(isPresented: .constant(true)) {
                SheetView()
                    .environment(\.scenePhase, scenePhase)
            }
    }
}

struct SheetView: View {
    @Environment (\.scenePhase) private var scenePhase
    var body: some View {
        NavigationView {
            NavigationLink("Show FirstDetailsView") {
                FirstDetailsView()
                    .environment(\.scenePhase, scenePhase)
            }
        }
    }
}

struct FirstDetailsView: View {
    @Environment (\.scenePhase) private var scenePhase
    var body: some View {
        NavigationLink("Show SecondDetailsView") {
            SecondDetailsView()
                .environment(\.scenePhase, scenePhase)
        }
        .onChange(of: scenePhase) { _ in
            print("scenePhase changed.")
        }
    }
}

struct SecondDetailsView: View {
    @Environment (\.scenePhase) private var scenePhase
    var body: some View {
        Text("This is SecondDetailsView")
    }
}
Fawzi Rifai
  • 533
  • 1
  • 4
  • 12
  • It seems, `scenePhase` changes will not be propagated through to a `PresentationHostingController` - which is the root view controller of a sheet and other presented views (alerts, etc.). In my humble opinion, this is a bug. Observed this for all iOS versions so far. Not yet tested in iOS 16 beta, though. In multiple Scenes the whole behaviour is erratically. There is limited usage only for a single Scene, because here, the issues are predictable ;) – CouchDeveloper Jul 04 '22 at 10:27

1 Answers1

7

The .sheet modifier introduces new presentation and environment is not copied there automatically. We have to do that explicitly, like

struct ContentView: View {
    @Environment (\.scenePhase) private var scenePhase
    var body: some View {
        Text("Hello, world!")
            .sheet(isPresented: .constant(true)) {
                SheetView()
                  .environment(\.scenePhase, scenePhase)   // << here !!
            }
    }
}

Tested with Xcode 13.4 / iOS 15.5

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thank you, it worked, but now I am trying to implement it in my real case which is ContentView > Sheet > FirstDetailsView > SecondDetailsView. I want to observe for scene changes from FirstSecondView which works when FirstSecondView is at the top of the navigation stack, but when I push SecondDetailsView to the top then the observer on FirstSecondView stops working. I will share the code in the question since I cannot share it here. I appreciate your help. – Fawzi Rifai Jun 14 '22 at 17:36