19

In a SwiftUI app I have code like this:

var body: some View {
    VStack {
        Spacer()
        ........
    }
    .onAppear {
      .... I want to have some code here ....
      .... to run when the view appears ....
    }
}

My problem is that I would like to run some code inside the .onAppear block, so that it gets run when the app appears on screen, after launching or after being in the background for a while. But it seems like this code is only run once at app launch, and never after. Am I missing something? Or should I use a different strategy to get the result I want?

Michel
  • 10,303
  • 17
  • 82
  • 179
  • 1
    it will get called everytime view appear – Jawad Ali Aug 15 '20 at 08:06
  • 1
    That is what I would like, but when I try it does not work like that. As explained in my post. – Michel Aug 15 '20 at 08:17
  • 1
    strange for me ... ca you share demo project to see ? – Jawad Ali Aug 15 '20 at 08:26
  • 1
    @jawadAli, start an app (using code similar to my post), then tap the home button, then tap the icon of the app to bring it back. You will see that the .onAppear code block is executed only once. The same is true if you come back to the app tapping on a universal link. – Michel Aug 15 '20 at 09:18

2 Answers2

20

If you're linking against iOS14 then you can take advantage of the new scenePhase concept:

@Environment(\.scenePhase) var scenePhase

Where ever you are the Environment if injecting this property that you can test against three conditions:

switch newPhase {
    case .inactive:
        print("inactive")
    case .active:
        print("active")
    case .background:
        print("background")
}

So, all together:

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

    var body: some View {
        Text("Hello, World!")
            .onChange(of: scenePhase) { newPhase in
                switch newPhase {
                    case .inactive:
                        print("inactive")
                    case .active:
                        print("active")
                    case .background:
                        print("background")
                }
            }
    }
}
valvoline
  • 7,737
  • 3
  • 47
  • 52
16

You would have to observe the event when the app is entering foreground and publish it using @Published to the ContentView. Here's how:

struct ContentView: View {

    @ObservedObject var observer = Observer()

    var body: some View {
        VStack {
            Spacer()
            //...
        }
        .onReceive(self.observer.$enteredForeground) { _ in
            print("App entered foreground!") // do stuff here
        }
    }
}

class Observer: ObservableObject {

    @Published var enteredForeground = true

    init() {
        if #available(iOS 13.0, *) {
            NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIScene.willEnterForegroundNotification, object: nil)
        } else {
            NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
        }
    }

    @objc func willEnterForeground() {
        enteredForeground.toggle()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}
Frankenstein
  • 15,732
  • 4
  • 22
  • 47
  • I need to spend a bit more time digging, but with the few tests I made, it already seems like this approach can lead to something. Using the method: func sceneWillEnterForeground(_ scene: UIScene) {}. – Michel Aug 15 '20 at 09:27
  • Yes, the above is just a glimpse of what can be achieved. You can modify it however you wish to obtain the results you like :) – Frankenstein Aug 15 '20 at 09:29
  • 1
    Yes, I am still beginning with SwiftUI. There seem to be a lot left that I don't quite understand at this point or may be don't even know about. ObservedObject, EnvironmentObject ... are among those. – Michel Aug 15 '20 at 10:43
  • SwiftUI has introduced a lot of great things to us. But, there isn't anything to worry about. You'll find these making more sense as you use them more frequently :) – Frankenstein Aug 15 '20 at 12:12
  • do we need to remove the observer in the deinit ? – Hatim Feb 10 '21 at 12:33
  • @Hatim Sure bro. You can use a simple `NotificationCenter.default.removeObserver(self)` in your `deinit` method. – Frankenstein Jul 02 '21 at 20:23