How to know if app is removed from background in SwiftUI? - ios

How to know if app is removed from background in SwiftUI? Is there any method just like Swift and how to use it?

I'd research the Scene protocol.
I quote...
The Scene protocol provides scene modifiers, defined as protocol
methods with default implementations, that you use to configure a
scene. For example, you can use the onChange(of:perform:) modifier to
trigger an action when a value changes. The following code empties a
cache when all of the scenes in the window group have moved to the
background:
and to be clear this is the example provided by Apple...
struct MyScene: Scene {
#Environment(\.scenePhase) private var scenePhase
#StateObject private var cache = DataCache()
var body: some Scene {
WindowGroup {
MyRootView()
}
.onChange(of: scenePhase) { newScenePhase in
if newScenePhase == .background {
cache.empty()
}
}
}
}
I place this in my App file, like in the following example (with Core Data implementation):
import SwiftUI
#main
struct MyApp: App {
let persistenceController = PersistenceController.shared
#Environment(\.scenePhase) var scenePhase
var body: some Scene {
WindowGroup {
NavigationView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
.onChange(of: scenePhase) { _ in
persistenceController.save()
}
}
}

Related

Passed in published property not firing onReceive in iOS 14 but is in iOS 16 [duplicate]

I have a custom ViewModifier which simply returns the same content attached with a onReceive modifier, the onReceive is not triggered, here is a sample code that you can copy, paste and run in XCode:
import SwiftUI
import Combine
class MyViewModel: ObservableObject {
#Published var myProperty: Bool = false
}
struct ContentView: View {
#ObservedObject var viewModel: MyViewModel
var body: some View {
Text("Hello, World!")
.modifier(MyOnReceive(viewModel: viewModel))
.onTapGesture {
self.viewModel.myProperty = true
}
}
}
struct MyOnReceive: ViewModifier {
#ObservedObject var viewModel: MyViewModel
func body(content: Content) -> some View {
content
.onReceive(viewModel.$myProperty) { theValue in
print("The Value is \(theValue)") // <--- this is not executed
}
}
}
is SwiftUI designed to disallow onReceive to execute inside a ViewModifier or is it a bug ? I have a view in my real life project that gets bigger with some business logic put inside onReceive, so I need to clean that view by separating it from onReceive.
ok, this works for me:
func body(content: Content) -> some View {
content
.onAppear() // <--- this makes it work
.onReceive(viewModel.$myProperty) { theValue in
print("-----> The Value is \(theValue)") // <--- this will be executed
}
}
ObservableObject and #Published are part of the Combine framework if you aren't using Combine then you shouldn't be using a class for your view data. Instead, you should be using SwiftUI as designed and avoid heavy objects and either put the data in the efficient View data struct or make a custom struct as follows:
import SwiftUI
struct MyConfig {
var myProperty: Bool = false
mutating func myMethod() {
myProperty = !myProperty
}
}
struct ContentView: View {
#State var config = MyConfig()
var body: some View {
Text("Hello, World!")
.onTapGesture {
config.myMethod()
}
}
}
Old answer:
Try onChange instead
https://developer.apple.com/documentation/swiftui/scrollview/onchange(of:perform:)
.onChange(of: viewModel.myProperty) { newValue in
print("newValue \(newValue)")
}
But please don't use the View Model object pattern in SwiftUI, try to force yourself to use value types like structs for all your data as SwiftUI was designed. Property wrappers like #State will give you the reference semantics you are used to with objects.

SwiftUI - When I create multiplatform app, how to change view in scene?

I am trying to create a multi-platform app from SwiftUI.
Here is my code in in
#main
struct MyApp: App
#State var sceneManager = SceneManager.shared
var body: some Scene {
WindowGroup {
if sceneManager.state == .landing {
LandingPageView()
} else if sceneManager.state == .historyRecord {
HistoryRecordView()
}
}
}
when my login did success, I will change SceneManager.shared property, from .landing to .historyRecord
but the view didn't change, how should I change root view of scene?
Thanks
SwiftUI 2.0 introduced StateObject for such purpose, so go with the following pattern
#StateObject var sceneManager = SceneManager.shared
and make
class SceneManager: ObservableObject {
#Published var state: StateTypeHere
}
Make your SceneManager an ObservableObject and change the #State wrapper to #ObservedObject.
Make sure state is an #Published variable.
https://developer.apple.com/documentation/combine/observableobject
Or you can change your #State to #State var sceneManagerState = SceneManager.shared.state

How to convert from UIKit life cycle to SwiftUI life cycle in iOS 14 (Xcode 12 Beta)

I am currently working on SwiftUI app in which I am using SceneDelegate and AppDelegate. I would like to know how I can convert the life cycle from UIKit to SwiftUI one where there is an App struct and with scenes etc.
Also I would like to know how to cater for CoreData and PersistentContainers and inject these into our environments.
Also I have used UIApplicationDelegateAdapter to inject AppDelegate but the #main is giving me error
'main()' is only available in iOS 14.0 or newer
I am using #available (iOS 14.0, *) in the beginning:
import SwiftUI
#available(iOS 14.0, *)
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Doing it like this, where does the SceneDelegate code goes. I am still quite confused how this conversion goes. I have not seen Apple talking about this in their sessions or anything. Help will be really appreciated.
where does the SceneDelegate code goes.
#available(iOS 14.0, *)
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup { // << this is a scene
ContentView()
.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 become inactive")
case .background:
print(">> your code is here on go in background")
default:
print(">> do something else in future")
}
}
}
}
}
Set the environment on the ContentView as follows:
import SwiftUI
import CoreData
#main
struct MasterDetailApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView().environment(\.managedObjectContext, appDelegate.persistentContainer.viewContext)
}
}
}

SwiftUI: ViewModifier doesn't listen to onReceive events

I have a custom ViewModifier which simply returns the same content attached with a onReceive modifier, the onReceive is not triggered, here is a sample code that you can copy, paste and run in XCode:
import SwiftUI
import Combine
class MyViewModel: ObservableObject {
#Published var myProperty: Bool = false
}
struct ContentView: View {
#ObservedObject var viewModel: MyViewModel
var body: some View {
Text("Hello, World!")
.modifier(MyOnReceive(viewModel: viewModel))
.onTapGesture {
self.viewModel.myProperty = true
}
}
}
struct MyOnReceive: ViewModifier {
#ObservedObject var viewModel: MyViewModel
func body(content: Content) -> some View {
content
.onReceive(viewModel.$myProperty) { theValue in
print("The Value is \(theValue)") // <--- this is not executed
}
}
}
is SwiftUI designed to disallow onReceive to execute inside a ViewModifier or is it a bug ? I have a view in my real life project that gets bigger with some business logic put inside onReceive, so I need to clean that view by separating it from onReceive.
ok, this works for me:
func body(content: Content) -> some View {
content
.onAppear() // <--- this makes it work
.onReceive(viewModel.$myProperty) { theValue in
print("-----> The Value is \(theValue)") // <--- this will be executed
}
}
ObservableObject and #Published are part of the Combine framework if you aren't using Combine then you shouldn't be using a class for your view data. Instead, you should be using SwiftUI as designed and avoid heavy objects and either put the data in the efficient View data struct or make a custom struct as follows:
import SwiftUI
struct MyConfig {
var myProperty: Bool = false
mutating func myMethod() {
myProperty = !myProperty
}
}
struct ContentView: View {
#State var config = MyConfig()
var body: some View {
Text("Hello, World!")
.onTapGesture {
config.myMethod()
}
}
}
Old answer:
Try onChange instead
https://developer.apple.com/documentation/swiftui/scrollview/onchange(of:perform:)
.onChange(of: viewModel.myProperty) { newValue in
print("newValue \(newValue)")
}
But please don't use the View Model object pattern in SwiftUI, try to force yourself to use value types like structs for all your data as SwiftUI was designed. Property wrappers like #State will give you the reference semantics you are used to with objects.

Update state of a screen struct from SceneDelegate

I'm coming from react-native and a beginner at Swift and SwiftUI and I was curious how to perform an action and update state on a specific screen when the app comes back into the foreground. I want to check the status of the notifications("allowed, "denied" etc.) and update the UI.
This is some example code - Here is the view I want to update:
struct Test: View {
#State var isNotificationsEnabled : Bool
var body : some View {
Toggle(isOn: self.isNotificationsEnabled) {
Text("Notifications")
}
}
}
So far what I've been reading is that you need to do edit the func sceneWillEnterForeground(_ scene: UIScene) inside SceneDelegate.swift but how on earth do I update the state of my Test struct from there? I'm thinking we need some kind of global state but that's just a guess.
Any advice?
Here is simplest approach
struct Test: View {
#State private var isNotificationsEnabled : Bool
private let foregroundPublisher = NotificationCenter.default.publisher(for: UIScene.willEnterForegroundNotification)
var body : some View {
Toggle(isOn: self.$isNotificationsEnabled) {
Text("Notifications")
}
.onReceive(foregroundPublisher) { notification in
// do anything needed here
}
}
}
Of course, if your application can have multiple scenes and you need to differentiate them somehow then more complicated variant of this approach will be needed to differentiate which scene generates this notification.

Resources