SwiftUI: Why doesn't ObservedObject work in AppDelegate?
When you create a subscription with .sink
you have to save the AnyCancellable
object returned
let cancellable = publisher.sink { ... }
And if you assign it to a variable, make sure it is not short lived. As soon as the cancellable object gets deallocated, the subscription will get cancelled too.
SwiftUI macOs AppDelegate detect the change of ObservedObject
There is a more sophisticated way to observe UserDefaults
.
The Combine
framework provides a publisher for UserDefaults
, which is actually a Key-Value Observing Publisher
First make your backgroundIsTransparent
property an extension of UserDefaults
extension UserDefaults {
@objc var backgroundIsTransparent: Bool {
get {
guard object(forKey: PreferencesKeys.backgroundIsTransparent) != nil else { return true }
return bool(forKey: PreferencesKeys.backgroundIsTransparent)
}
set {
set(newValue, forKey: PreferencesKeys.backgroundIsTransparent)
}
}
}
In AppDelegate import Combine
and create a Set
for the subscription(s)
import Combine
@main
class AppDelegate: NSObject, NSApplicationDelegate { ...
private var subscriptions = Set<AnyCancellable>()
and in applicationDidFinishLaunching
add the publisher
func applicationDidFinishLaunching(_ aNotification: Notification) {
// .....
UserDefaults.standard.publisher(for: \.backgroundIsTransparent)
.sink { state in
print(state)
// do something with state
}
.store(in: &subscriptions)
}
An alternative using your approach is to make userPreferences
a @StateObject
@StateObject var userPreferences = UserPreferences.instance
and observe backgroundIsTransparent
userPreferences.$backgroundIsTransparent
.sink { state in
print(state)
// do something with state
}
.store(in: &subscriptions)
How does $ work in SwiftUI and why can I cast a @StateObject to an @ObservedObject
@StateObject
and @ObservedObject
are sources of truth, they are very similar to each other and almost interchangeable.
To share these with other View
s you should pass it to the AddFoodView
using .environmentObject(fridge)
and change the @ObservedObject var fridgeView: Fridge;
in AddFoodView
to @EnvironmentObject var fridgeView: Fridge
.
Look up the Apple Documentation on "Managing Model Data in Your App".
struct BookReader: App {
@StateObject var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
struct LibraryView: View {
@EnvironmentObject var library: Library
// ...
}
Another good source are the Apple SwiftUI Tutorials, especially "Handling User Input"
SwiftUI doesn't update state to @ObservedObject cameraViewModel object
The problem with your provided code is that the type of selectedFocus
within the FocusPicker
view should be Integer rather than Float. So one option is to change this type to Integer and find a way to express the AVCaptureDevice.lensPosition
as an Integer with the given range.
The second option is to replace the feets array with an enumeration. By making the enumeration conform to the CustomStringConvertible
protocol, you can even provide a proper description. Please see my example below.
I've stripped your code a bit as you just wanted to display the number in the first step and thus the code is more comprehensible.
My working example:
import SwiftUI
import Combine
struct ContentView: View {
@ObservedObject var cameraViewModel = CameraViewModel(focusLensPosition: 0.5)
var body: some View {
VStack {
ZStack {
VStack {
FocusPicker(selectedFocus: $cameraViewModel.focusLensPosition)
Text(String(self.cameraViewModel.focusLensPosition))
.foregroundColor(.red)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.edgesIgnoringSafeArea(.all)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class CameraViewModel: ObservableObject {
@Published var focusLensPosition: Float
init(focusLensPosition: Float) {
self.focusLensPosition = focusLensPosition
}
}
enum Feets: Float, CustomStringConvertible, CaseIterable, Identifiable {
case case1 = 0.0
case case2 = 0.5
case case3 = 1.0
var id: Float { self.rawValue }
var description: String {
get {
switch self {
case .case1:
return "∞ ft"
case .case2:
return "4"
case .case3:
return "Auto"
}
}
}
}
struct FocusPicker: View {
@Binding var selectedFocus: Float
var body: some View {
Picker(selection: $selectedFocus, label: Text("")) {
ForEach(Feets.allCases) { feet in
Text(feet.description)
}
.animation(.none)
.background(Color.clear)
.pickerStyle(WheelPickerStyle())
}
.frame(width: 60, height: 200)
.border(Color.gray, width: 5)
.clipped()
}
}
SwiftUI: Access @EnvironmentObject from AppDelegate
An @EnvironmentObject
doesn't need to be instantiated directly in your SwiftUI objects; rather, it can be allocated somewhere else (for example, your UISceneDelegate) and then passed through using the .environment(…)
function.
You could also allocate it on your AppDelegate, and pass that object though to your views in UISceneDelegate.scene(_:willConectTo:options:)
method.
Paul Hudson has a good description of all of this at https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views
iOS SwiftUI: ObservableObject from parent to child
Instead of passing the same model to each subview, you can use @EnvironmentObject
instead which is specifically designed for this.
You essentially have to inject your ObservableObject
in your parent view with something like this (typically in your appdelegate before presenting the parent view, or in your previews setup for live previewing):
Parent().environmentObject(ViewModel())
then you can automatically get a reference in each view with something like this:
struct AnyChild: View {
@EnvironmentObject var model: ViewModel
//...
}
and use it with your bindings
Here is a brief article about the use of environment
Finally, regarding why your solution is not working, you must consider a single source of truth (the observable object in your Parent) and a @Binding
for each subview that will eventually change the state of this object. I hope that this makes sense...
Xcode SwiftUI window not shown after successful build
This might be due to a bug.
I removed all the other schemes
but the one I was using in the project and the window is now loading.
Yet, the same warning is still there.
Related Topics
Can't Set @Ibinspectable Computed Property in UIview
Print Not Working in Swift 3 Extensions
No Trailing Closures Support for Methods with Default Parameter Values
How to Horizontally Center Content of Horizontal Scrollview
Compiler Segmentation Fault While Using Set in Swift
Swift: Trunc a Floating Number to Show It in a Label
Swiftui Go Back Programmatically from Representable to View
Swift Initialization Rule Confusion
How to Convert an Array to List in Realm
How to Calculate The Camera Position from Vuforia Gl Matrix
Why Does a Simple Swift Arithmetic Operation Compile So Slow
Calling Consecutive Animations in Swift with Completion Handler
How to Specify The Name of The Output Executable
Swift Error "Static Member Cannot Be Used on Instance of Type"
Pie Chart Entries Outside Slices Have Different Position Offsets
Onreceive String.Publisher Lead to Infinite Loop
Adding Constraints Programmatically in UIview with UItextview