Observation

RSS for tag

Make responsive apps that update the presentation when underlying data changes.

Posts under Observation tag

110 Posts

Post

Replies

Boosts

Views

Activity

No ObservableObject of Type "" found.
Im building an recipe app for the social media of my mother. i already have the functionality for the users, when a user gets created an empty array gets initiated at the database named favoriteRecipes, which stores the id of his favorite recipes to show in a view. This is my AuthViewModel which is relevant for the user stuff: import Firebase import FirebaseAuth import FirebaseFirestore protocol AuthenticationFormProtocol { var formIsValid: Bool { get } } @MainActor class AuthViewModel : ObservableObject { @Published var userSession: FirebaseAuth.User? @Published var currentUser: User? @Published var currentUserId: String? init() { self.userSession = Auth.auth().currentUser Task { await fetchUser() } } func signIn(withEmail email: String, password: String) async throws { do { let result = try await Auth.auth().signIn(withEmail: email, password: password) self.userSession = result.user await fetchUser() // fetch user sonst profileview blank } catch { print("DEBUG: Failed to log in with error \(error.localizedDescription)") } } func createUser(withEmail email: String, password: String, fullName: String) async throws { do { let result = try await Auth.auth().createUser(withEmail: email, password: password) self.userSession = result.user let user = User(id: result.user.uid, fullName: fullName, email: email) let encodedUser = try Firestore.Encoder().encode(user) try await Firestore.firestore().collection("users").document(result.user.uid).setData(encodedUser) await fetchUser() } catch { print("Debug: Failed to create user with error \(error.localizedDescription)") } } func signOut() { do { try Auth.auth().signOut() // sign out user on backend self.userSession = nil // wipe out user session and take back to login screen self.currentUser = nil // wipe out current user data model } catch { print("DEBUG: Failed to sign out with error \(error.localizedDescription)") } } func deleteAcocount() { let user = Auth.auth().currentUser user?.delete { error in if let error = error { print("DEBUG: Error deleting user: \(error.localizedDescription)") } else { self.userSession = nil self.currentUser = nil } } } func fetchUser() async { guard let uid = Auth.auth().currentUser?.uid else { return } currentUserId = uid let userRef = Firestore.firestore().collection("users").document(uid) do { let snapshot = try await userRef.getDocument() if snapshot.exists { self.currentUser = try? snapshot.data(as: User.self) print("DEBUG: current user is \(String(describing: self.currentUser))") } else { // Benutzer existiert nicht mehr in Firebase, daher setzen wir die userSession auf nil self.userSession = nil self.currentUser = nil } } catch { print("DEBUG: Fehler beim Laden des Benutzers: \(error.localizedDescription)") } } } This is the code to fetch the favorite recipes, i use the id of the user to access the collection and get the favoriteRecipes out of the array: import SwiftUI @MainActor class FavoriteRecipeViewModel: ObservableObject { @Published var favoriteRecipes: [Recipe] = [] @EnvironmentObject var viewModel: AuthViewModel private var db = Firestore.firestore() init() { Task { await fetchFavoriteRecipes() } } func fetchFavoriteRecipes() async{ let userRef = db.collection("users").document(viewModel.userSession?.uid ?? "") do { let snapshot = try await userRef.collection("favoriteRecipes").getDocuments() let favoriteIDs = snapshot.documents.map { $0.documentID } let favoriteRecipes = try await fetchRecipes(recipeIDs: favoriteIDs) } catch { print("DEBUG: Failed to load favorite recipes for user: \(error.localizedDescription)") } } func fetchRecipes(recipeIDs: [String]) async throws -> [Recipe] { var recipes: [Recipe] = [] for id in recipeIDs { let snapshot = try await db.collection("recipes").document(id).getDocument() if let recipe = try? snapshot.data(as: Recipe.self) { recipes.append(recipe) } } return recipes } } Now the Problem occurs at the build of the project, i get the error SwiftUICore/EnvironmentObject.swift:92: Fatal error: No ObservableObject of type AuthViewModel found. A View.environmentObject(_:) for AuthViewModel may be missing as an ancestor of this view. I already passed the ViewModel instances as EnvironmentObject in the App Struct. import SwiftUI import FirebaseCore class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { FirebaseApp.configure() return true } } @main struct NimetAndSonApp: App { @StateObject var viewModel = AuthViewModel() @StateObject var recipeViewModel = RecipeViewModel() @StateObject var favoriteRecipeViewModel = FavoriteRecipeViewModel() @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate var body: some Scene { WindowGroup { ContentView() .environmentObject(viewModel) .environmentObject(recipeViewModel) .environmentObject(favoriteRecipeViewModel) } } }
1
0
691
Nov ’24
@Observable in command line app
I have a problem with the following code, I am not being notified of changes to the progress property of my Job object, which is @Observable... This is a command-line Mac application (the same code works fine in a SwiftUI application). I must have missed something? do { let job = AsyncJob() withObservationTracking { let progress = job.progress } onChange: { print("Current progress: \(job.progress)") } let _ = try await job.run() print("Done...") } catch { print(error) } I Try this without any success: @main struct MyApp { static func main() async throws { // my code here } }
10
1
1.2k
Oct ’24
Do update to @Observable properties have to be done on the main thread?
According to this old thread the answer is no. But I never understood why. In the old world. It was always required that you make changes to @Published properties on the main thread. In fact compiler would complain. In the main world, can you just update that in the background thread? And then SwiftUI take cares of refreshing the views on the main thread? So I guess that begs that question, why did it used to require it for @Published? Furthermore, I have recently gotten new crashes when update is done from background but I can't be sure it's related: For example I have the following, and the crash is as follows: @Observable class PlanViewModel { var stagingPlan: Plan? func savePlan() async { //some code here.... stagingPlan = nil //crash } } Is this issue potentially related to main thread? Should I do that assignment forcefully on main thread? call stack 1 call stack 2 call stack 3 I dont know how to troubleshoot this further as xcode doesnt provide me any info other than that one red line
1
0
622
Oct ’24
Binding with ForEach or List doesn't work anymore when using @Observable macro
Hi. The binding in a ForEach or List view doesn't work anymore when using the @Observable macro to create the observable object. For example, the following are the modifications I introduced to the Apple's example called "Migrating from the Observable Object Protocol to the Observable Macro" https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro struct LibraryView: View { @Environment(Library.self) private var library var body: some View { List($library.books) { $book in BookView(book: book) } } } All I did was to add the $ to turn the reference to library.books into a binding but I got the error "Cannot find '$library' in scope" Is this a bug or the procedure to use binding in lists changed? Thanks
3
0
2.4k
Oct ’24
Is @ObservationIgnored necessary for private var?
In SwiftUI's ViewModel class that are @Observable, is it necessary to annotate private fields as @ObservationIgnored? I'm not sure if adding @ObservationIgnored to these fields will get performance gains, since there are no SwiftUI structs referencing these fields because they're private. However, I'd like to know what's the recommended approach here? While this might not seem obvious for the example below, however, sometimes I have private fields that are changing pretty frequently. For these frequently changed fields, I think the performance gains will be larger. Example: @Observable class UserProfileViewModel { var userName: String? var userPhoneNumber: String? private var isFetchingData = false } vs @Observable class UserProfileViewModel { var userName: String? var userPhoneNumber: String? @ObservationIgnored private var isFetchingData = false }
2
0
2k
Oct ’24
Struggling with Swift Gesture/Observation
I'm trying to create an equivalent to TabView, but with the difference that the 2nd View slides in over the top of the primary view. Maybe there's a more elegant way of coding this (suggestions appreciated), but I've almost succeeded using the dragGesture. When a user swipes right to left the observed variable showTab2 is set to true, and the 2nd tab glides in over the top of tab 1 and displays 🥳. The only problem is, that when a user happens to start the swipe over a button, the observed status (showTab2) does change as expected but the main view does not catch this change and does not display tab2. And that despite the showTab2 being an @Observable. Any clues what I've missed? Or how to capture that the start of a swipe gesture starts over the top of a button and should be ignored. According to the code in SwipeTabView this screenshot 👆 should never occur. Here's the code: @Observable class myclass { var showTab2 = false } struct SwipeTabView: View { @State var myClass = myclass() @State var dragAmount: CGSize = CGSize.zero var body: some View { VStack { ZStack { GeometryReader { geometryProxy in VStack { tab(tabID: 1, selectedTab: myClass.showTab2) .zIndex(/*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) .background(.black) .transition(.identity) .swipeable(stateOfViewAdded: $myClass.showTab2, dragAmount: $dragAmount, geometryProxy: geometryProxy, insertion: true) } if myClass.showTab2 || dragAmount.width != 0 { tab(tabID: 2, selectedTab: myClass.showTab2) .zIndex(2.0) .drawingGroup() .transition(.move(edge: .trailing)) .offset(x: dragAmount.width ) .swipeable(stateOfViewAdded: $myClass.showTab2, dragAmount: $dragAmount, geometryProxy: geometryProxy, insertion: false) } } } } } } extension View { func swipeable(stateOfViewAdded: Binding<Bool>, dragAmount: Binding<CGSize>, geometryProxy: GeometryProxy, insertion: Bool) -> some View { self.gesture( DragGesture() .onChanged { gesture in // inserting must be minus, but removing must be positive - hence the multiplication. if gesture.translation.width * (insertion ? 1 : -1 ) < 0 { if insertion { dragAmount.wrappedValue.width = geometryProxy.size.width + gesture.translation.width } else { dragAmount.wrappedValue.width = gesture.translation.width } } } .onEnded { gesture in if abs(gesture.translation.width) > 100.0 && gesture.translation.width * (insertion ? 1 : -1 ) < 0 { withAnimation(.easeOut.speed(Double(gesture.velocity.width))) { stateOfViewAdded.wrappedValue = insertion } } else { withAnimation(.easeOut.speed(Double(gesture.velocity.width))) { stateOfViewAdded.wrappedValue = !insertion } } withAnimation(.smooth) { dragAmount.wrappedValue = CGSize.zero } } ) } } struct tab: View { var tabID: Int var selectedTab: Bool var body: some View { ZStack { Color(tabID == 1 ? .yellow : .orange) VStack { Text("Tab \(tabID) ").foregroundColor(.black) Button(action: { print("Tab2 should display - \(selectedTab.description)") }, label: { ZStack { circle label } }) Text("Tab2 should display - \(selectedTab.description)") } } } var circle: some View { Circle() .frame(width: 100, height: 100) .foregroundColor(.red) } var label: some View { Text("\(tabID == 1 ? ">>" : "<<")").font(.title).foregroundColor(.black) } }
4
0
818
Sep ’24
SwiftUI and SwiftData @Query
Hey everyone, I’m relatively new to SwiftUI and iOS development, having started earlier this year, and I’m working on a Notes app using SwiftData. I’ve run into some issues when dealing with nested views. In my app, I’m using a SwiftData @Query in a parent view and then passing the model objects down the view tree. However, I’m frequently encountering errors such as “No container found” or similar. To work around this, I’ve tried having the child views perform their own SwiftData @Query, with the parent view passing a string identifier to the child views. This approach has helped somewhat, but I’m still not entirely sure how to properly manage UI updates. Additionally, I’ve noticed that turning on iCloud syncing seems to have made the problem worse. The errors have become more frequent, and it’s unclear to me how to handle these situations effectively. Could someone explain the best practices for handling SwiftData queries in nested SwiftUI views, especially with iCloud syncing enabled? Any guidance or feedback would be greatly appreciated. Thank you!
1
0
1.5k
Aug ’24
How to observe SwiftData DB changes
How can one observe changes in the SwiftData DB? I'm aware that this is possible via Queries, but I need to fetch the data in background, so a query is not an option. I'm aware of the ModelContext.didSave / .willSave notifications, but these don't work with iOS 17. -> How can I observe changes to models of a certain type? I don't want to observe the whole database.
0
3
919
Aug ’24
SwiftUI State not reliable updating
Hello, I have a SwiftUI view with the following state variable: @State private var startDate: Date = Date() @State private var endDate: Date = Date() @State private var client: Client? = nil @State private var project: Project? = nil @State private var service: Service? = nil @State private var billable: Bool = false Client, Project, and Service are all SwiftData models. I have some view content that binds to these values, including Pickers for the client/project/service and a DatePicker for the Dates. I have an onAppear listener: .onAppear { switch state.mode { case .editing(let tt): Task { await MainActor.run { startDate = tt.startDate endDate = tt.endDate client = tt.client project = tt.project service = tt.service billable = tt.billable } } default: return } } This works as expected. However, if I remove the Task & MainActor.run, the values do not fully update. The DatePickers show the current date, the Pickers show a new value but tapping on them shows a nil default value. What is also extremely strange is that if tt.billable is true, then the view does update as expected. I am using Xcode 15.4 on iOS simulator 17.5. Any help would be appreciated.
1
0
758
Aug ’24
NavigationSplitView freezes
DESCRIPTION OF PROBLEM I have changed my app to the @Observable-Macro. When using an iPhone (on simulator and on real device) the navigation from a player to the player detail view and back breaks. In the attached video on my GitHub you can see me tapping on both players in the team, but the navigation ist not showing the detail view. What is the reason? Is my usage/understanding of @Observable wrong? Is it wrong to have the selectedPlayer within the PlayerController which is @Observable? And why does it sometimes work and sometimes not? The project can be found here: GitHub Project STEPS TO REPRODUCE Start the App, add one or two demo teams, tap on a team and add two or more demo players. tap a player and then go back, tap the player again and back again. After a while (number of taps is always different), the navigation breaks. See my video attached. PLATFORM AND VERSION iOS Development environment: Xcode 15.4, macOS 14.5 (23F79) Run-time configuration: iOS 17.5,
4
0
690
Aug ’24
@State ViewModel memory leak in iOS 17 (new Observable)
Our app has an architecture based on ViewModels. Currently, we are working on migrating from the ObservableObject protocol to the Observable macro (iOS 17+). The official docs about this are available here: https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro Our ViewModels that were previously annotated with @StateObject now use just @State, as recommended in the official docs. Some of our screens (a screen is a SwiftUI view with a corresponding ViewModel) are presented modally. We expect that after dismissing a SwiftUI view that was presented modally, its corresponding ViewModel, which is owned by this view (via the @State modifier), will be deinitialized. However, it seems there is a memory leak, as the ViewModel is not deinitialized after a modal view is dismissed. Here's a simple code where ModalView is presented modally (through the .sheet modifier), and ModalViewModel, which is a @State of ModalView, is never deinitialized. import SwiftUI import Observation @Observable final class ModalViewModel { init() { print("Simple ViewModel Inited") } deinit { print("Simple ViewModel Deinited") // never called } } struct ModalView: View { @State var viewModel: ModalViewModel = ModalViewModel() let closeButtonClosure: () -> Void var body: some View { ZStack { Color.yellow .ignoresSafeArea() Button("Close") { closeButtonClosure() } } } } struct ContentView: View { @State var presentSheet: Bool = false var body: some View { Button("Present sheet modally") { self.presentSheet = true } .sheet(isPresented: $presentSheet) { ModalView { self.presentSheet = false } } } } #Preview { ContentView() } Is this a bug in the iOS 17 beta version or intended behavior? Is it possible to build a relationship between the View and ViewModel in a way where the ViewModel will be deinitialized after the View is dismissed? Thank you in advance for the help.
4
12
3.4k
Jul ’24
Confusing SwiftUI error log: "Mutating observable property after view is torn down has no effect"
Hey, I have a setup in my app that I am currently building, that uses @Observable router objects that hold the app's entire navigation state, so that I can easily set it globally and let SwiftUI show the appropriate views accordingly. Each view gets passed in such a router object and there is a global "app" router that the app's root view can access as an entry point: @Observable @MainActor final class AppRouter { static let shared = AppRouter() // Entry point used by the app's root view var isShowingSheet = false // Navigation state let sheetRouter = SheetRouter() // Router that's passed to the sheet view. This router could contain other routers for sheets it will show, and so on } @Observable @MainActor final class SheetRouter { // Example of a "nested" router for a sheet view var path = NavigationPath() var isShowingOtherSheet = false func reset() { path = .init() isShowingOtherSheet = false } } To open a sheet, I have a button like this: @Bindable var appRouter = AppRouter.shared // ... Button("Present") { appRouter.sheetRouter.reset() // Reset sheet router appRouter.isShowingSheet = true // show sheet } This seems to work perfectly fine. However, this produces tons of "error" logs in the console, whenever I open the sheet for a second time: Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.isShowingOtherSheet after view is torn down has no effect. These errors appear when calling the reset() of the sheet view's router before opening the sheet. That method simply resets all navigation states in a router back to their defaults. It's as if the sheetRouter is still connected to the dismissed view from the previous sheet, causing a mutation to trigger these error logs. Am I misusing SwiftUI here or is that a bug? It's also worth mentioning that these error logs do not appear on iOS 17. Only on iOS 18. So it might be a bug but I just want to make sure my usage of these router objects is okay and not a misuse of the SwiftUI API that the runtime previously simply did not catch/notice. Then I'd have to rewrite my entire navigation logic. I do have an example project to demonstrate the issue. You can find it here: https://github.com/SwiftedMind/PresentationBugDemo. I have also filed a feedback for this: FB14162780 STEPS TO REPRODUCE Open the example project. Open the sheet in the ContentView twice by tapping "Present" Close that sheet Open it again. Then the console will show these error logs from above. I'd appreciate any help with this. Cheers
2
2
1.1k
Jul ’24
Using @Bindable with a Observable type
Originally asked on Swift Forums: https://forums.swift.org/t/using-bindable-with-a-observable-type/70993 I'm using SwiftUI environments in my app to hold a preferences object which is an @Observable object But I want to be able to inject different instances of the preferences object for previews vs the production code so I've abstracted my production object in to a Preferences protocol and updated my Environment key's type to: protocol Preferences { } @Observable final class MyPreferencesObject: Preferences { } @Observable final class MyPreviewsObject: Preferences { } // Environment key private struct PreferencesKey: EnvironmentKey { static let defaultValue : Preferences & Observable = MyPreferencesObject() } extension EnvironmentValues { var preferences: Preferences & Observable { get { self[PreferencesKey.self] } set { self[PreferencesKey.self] = newValue } } } The compiler is happy with this until I go to use @Bindable in my code where the compiler explodes with a generic error, eg: @Environment(\.preferences) private var preferences // ... code @Bindable var preferences = preferences If I change the environment object back to a conforming type eg: @Observable final class MyPreferencesObject() { } private struct PreferencesKey: EnvironmentKey { static let defaultValue : MyPreferencesObject = MyPreferencesObject() } extension EnvironmentValues { var preferences: MyPreferencesObject { get { self[PreferencesKey.self] } set { self[PreferencesKey.self] = newValue } } } Then @Bindable is happy again and things compile. Is this a known issue/limitation? Or am I missing something here?
3
1
1.1k
Jun ’24
[sample code]when model changed, the view in SwiftUI isn’t update
in the SampleTrips, for the pure SwiftData version, when I add a living accommodation from the trip detail view, the trip detail view won’t update until I go back to the trip list view and enter again. why didn’t the trip detail view update? I have tested the pure CoreData version in sample code, it worked well. is this a bug or a feature of SwiftData?
1
0
876
Jun ’24
SwiftUI - Using Bindable with Environment
At the moment, using Bindable for an object stored in Environment works in a cumbersome way: struct ContentView: View { @Environment(Model.self) var model var body: some View { @Bindable var model = model VStack { Text(model.someField.uppercased()) TextField("", text: $model.someField) someSubView } .padding() } @ViewBuilder var someSubView: some View { @Bindable var model = model TextField("", text: $model.someField) } } A new @Bindable needs to be instantiated for each computed property in the view, which creates boilerplate I would like to avoid. I made a new property wrapper which functions the same as the EnvironmentObject wrapper, but for Observable: @propertyWrapper struct EnvironmentObservable<Value: AnyObject & Observable>: DynamicProperty { @Environment var wrappedValue: Value public init(_ objectType: Value.Type) { _wrappedValue = .init(objectType) } public init() { _wrappedValue = .init(Value.self) } private var store: Bindable<Value>! var projectedValue: Bindable<Value> { store } mutating func update() { store = Bindable(wrappedValue) } } Example: struct ContentView: View { @EnvironmentObservable var model: Model var body: some View { VStack { Text(model.someField.uppercased()) SubView(value: $model.someField) someSubView } .padding() } var someSubView: some View { TextField("", text: $model.someField) } } I was wondering if there would be any downsides to using this method? In my testings it seems to behave the same, but I'm not sure if using this could have a performance impact.
1
2
1k
Jun ’24
I wonder swiftdata query cannot be used within a class
import SwiftUI import SwiftData class DateManagerStore : ObservableObject { @Query private var myData: [myData] @Published var myDataToString = "" func hopitalDataQuery() { if let lastMyData = myData { self.myDataToString = String(lastMyData.sorted(by: {$0.visitedDate > $1.visitedDate}).last) } } } struct MainView: View { @EnvironmentObject var dateManagerStore : DateManagerStore var body: some View { VStack{ Text("\(dateManagerStore.myDataToString)") } .onAppear(perform: { dateManagerStore.hopitalDataQuery() }) } } I thought it would be good to manage SwiftData values ​​used within multiple views in one place. I wanted to use Query data in the DateManagerStore class declared as ObservableObject through onApper of the MainView. However, when printing the myData variable within hopitalDataQuery() of the DateManagerStore class, empty data was output. I tried to use @Query defined inside the DateManagerStore class in various ways, but none of the methods allowed me to put a value into the @Query variable 'myData'. There is no error in Xcode itself, but no data is coming in. I can't find any related information anywhere, so I ask if it's officially not possible.
3
2
2k
May ’24
@Observable conflict with @Query
If I annotate a class with @Observable I get this error in @Query: Expansion of macro 'Query()' produced an unexpected 'init' accessor If I remove @Observable the error goes away. Elsewhere I have .environment referencing the class. With @Observable this complains that the class needs to be @Observable. I am mystified. Does anyone have a suggestion?
4
0
1.2k
May ’24
Alternative option to initial onChange callback for EmptyView
I am exploring on managing state in SwiftUI app with purpose built Views due to the advantages for managing dependency with Environment. This is the minimal example I came up with: @MainActor struct AsyncStateModifier<T: Equatable>: View { let input: T let action: (T) async -> Void @Environment var queue: AsyncActionQueue var body: some View { return EmptyView() .onChange(of: input, initial: true) { old, new in queue.process(action: action, with: input) } } } The drawback of this approach is initial: true allows the onChange callback to fire when view appears and since EmptyView doesn't appear the action is never executed initially. When replacing EmptyView with Rectangle().hidden() this can be achieved, but I wanted to avoid having any impact on view hierarchy and EmptyView is suitable for that. Is there any alternative approach to make something like this possible?
1
0
807
May ’24
Is it true that @State is used for Value types and @StateObject is used for Reference types?
I am little confused about when to use State / StateObject / ObservedObject. What I have researched and what I understand: @State --> for value types @StateObject --> for reference types @ObservedObject --> child objects who needs reference to above two (the parent object should have @State/@StateObject and the object should conform to Observable) I am clear about Environment object.
1
0
610
May ’24
Migrating @MainActor ViewModel to @Observable causing error
I get this error while migrating from ObservableObject to @Observable. Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context My original code: struct SomeView: View { @StateObject private var viewModel = ViewModel() } After migration: @MainActor @Observable class BaseViewModel { } @MainActor class ViewModel: BaseViewModel { } struct SomeView: View { @State private var viewModel = ViewModel() } As discussed here. It seems like @StateObject is adding @MainActor compliance to my View under the hood because it's wrappedValue and projectedValue properties are marked as @MainActor, while on @State they are not. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @frozen @propertyWrapper public struct StateObject&lt;ObjectType&gt; : DynamicProperty where ObjectType : ObservableObject { ... @MainActor public var wrappedValue: ObjectType { get } .... @MainActor public var projectedValue: ObservedObject&lt;ObjectType&gt;.Wrapper { get } } One solution for this is to mark my View explicitly as @MainActor struct ViewModel: View but it have it side effects, for example code like: Button(action: resendButtonAction) { Text(resendButtonAttributedTitle()) } Will result a warning Converting function value of type '@MainActor () -&gt; ()' to '() -&gt; Void' loses global actor 'MainActor' While could be easily solved by using instead Button(action: { resendButtonAction() } ) { Text(resendButtonAttributedTitle()) } I still feel like marking the whole View explicitly as @MainActor is not a good practice. Adding fake @StateObject property to my view also do the trick, but it's a hack (the same for @FetchRequest). Can anyone think of a more robust solution for this?
1
1
2.2k
May ’24
No ObservableObject of Type "" found.
Im building an recipe app for the social media of my mother. i already have the functionality for the users, when a user gets created an empty array gets initiated at the database named favoriteRecipes, which stores the id of his favorite recipes to show in a view. This is my AuthViewModel which is relevant for the user stuff: import Firebase import FirebaseAuth import FirebaseFirestore protocol AuthenticationFormProtocol { var formIsValid: Bool { get } } @MainActor class AuthViewModel : ObservableObject { @Published var userSession: FirebaseAuth.User? @Published var currentUser: User? @Published var currentUserId: String? init() { self.userSession = Auth.auth().currentUser Task { await fetchUser() } } func signIn(withEmail email: String, password: String) async throws { do { let result = try await Auth.auth().signIn(withEmail: email, password: password) self.userSession = result.user await fetchUser() // fetch user sonst profileview blank } catch { print("DEBUG: Failed to log in with error \(error.localizedDescription)") } } func createUser(withEmail email: String, password: String, fullName: String) async throws { do { let result = try await Auth.auth().createUser(withEmail: email, password: password) self.userSession = result.user let user = User(id: result.user.uid, fullName: fullName, email: email) let encodedUser = try Firestore.Encoder().encode(user) try await Firestore.firestore().collection("users").document(result.user.uid).setData(encodedUser) await fetchUser() } catch { print("Debug: Failed to create user with error \(error.localizedDescription)") } } func signOut() { do { try Auth.auth().signOut() // sign out user on backend self.userSession = nil // wipe out user session and take back to login screen self.currentUser = nil // wipe out current user data model } catch { print("DEBUG: Failed to sign out with error \(error.localizedDescription)") } } func deleteAcocount() { let user = Auth.auth().currentUser user?.delete { error in if let error = error { print("DEBUG: Error deleting user: \(error.localizedDescription)") } else { self.userSession = nil self.currentUser = nil } } } func fetchUser() async { guard let uid = Auth.auth().currentUser?.uid else { return } currentUserId = uid let userRef = Firestore.firestore().collection("users").document(uid) do { let snapshot = try await userRef.getDocument() if snapshot.exists { self.currentUser = try? snapshot.data(as: User.self) print("DEBUG: current user is \(String(describing: self.currentUser))") } else { // Benutzer existiert nicht mehr in Firebase, daher setzen wir die userSession auf nil self.userSession = nil self.currentUser = nil } } catch { print("DEBUG: Fehler beim Laden des Benutzers: \(error.localizedDescription)") } } } This is the code to fetch the favorite recipes, i use the id of the user to access the collection and get the favoriteRecipes out of the array: import SwiftUI @MainActor class FavoriteRecipeViewModel: ObservableObject { @Published var favoriteRecipes: [Recipe] = [] @EnvironmentObject var viewModel: AuthViewModel private var db = Firestore.firestore() init() { Task { await fetchFavoriteRecipes() } } func fetchFavoriteRecipes() async{ let userRef = db.collection("users").document(viewModel.userSession?.uid ?? "") do { let snapshot = try await userRef.collection("favoriteRecipes").getDocuments() let favoriteIDs = snapshot.documents.map { $0.documentID } let favoriteRecipes = try await fetchRecipes(recipeIDs: favoriteIDs) } catch { print("DEBUG: Failed to load favorite recipes for user: \(error.localizedDescription)") } } func fetchRecipes(recipeIDs: [String]) async throws -&gt; [Recipe] { var recipes: [Recipe] = [] for id in recipeIDs { let snapshot = try await db.collection("recipes").document(id).getDocument() if let recipe = try? snapshot.data(as: Recipe.self) { recipes.append(recipe) } } return recipes } } Now the Problem occurs at the build of the project, i get the error SwiftUICore/EnvironmentObject.swift:92: Fatal error: No ObservableObject of type AuthViewModel found. A View.environmentObject(_:) for AuthViewModel may be missing as an ancestor of this view. I already passed the ViewModel instances as EnvironmentObject in the App Struct. import SwiftUI import FirebaseCore class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -&gt; Bool { FirebaseApp.configure() return true } } @main struct NimetAndSonApp: App { @StateObject var viewModel = AuthViewModel() @StateObject var recipeViewModel = RecipeViewModel() @StateObject var favoriteRecipeViewModel = FavoriteRecipeViewModel() @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate var body: some Scene { WindowGroup { ContentView() .environmentObject(viewModel) .environmentObject(recipeViewModel) .environmentObject(favoriteRecipeViewModel) } } }
Replies
1
Boosts
0
Views
691
Activity
Nov ’24
@Observable in command line app
I have a problem with the following code, I am not being notified of changes to the progress property of my Job object, which is @Observable... This is a command-line Mac application (the same code works fine in a SwiftUI application). I must have missed something? do { let job = AsyncJob() withObservationTracking { let progress = job.progress } onChange: { print("Current progress: \(job.progress)") } let _ = try await job.run() print("Done...") } catch { print(error) } I Try this without any success: @main struct MyApp { static func main() async throws { // my code here } }
Replies
10
Boosts
1
Views
1.2k
Activity
Oct ’24
Do update to @Observable properties have to be done on the main thread?
According to this old thread the answer is no. But I never understood why. In the old world. It was always required that you make changes to @Published properties on the main thread. In fact compiler would complain. In the main world, can you just update that in the background thread? And then SwiftUI take cares of refreshing the views on the main thread? So I guess that begs that question, why did it used to require it for @Published? Furthermore, I have recently gotten new crashes when update is done from background but I can't be sure it's related: For example I have the following, and the crash is as follows: @Observable class PlanViewModel { var stagingPlan: Plan? func savePlan() async { //some code here.... stagingPlan = nil //crash } } Is this issue potentially related to main thread? Should I do that assignment forcefully on main thread? call stack 1 call stack 2 call stack 3 I dont know how to troubleshoot this further as xcode doesnt provide me any info other than that one red line
Replies
1
Boosts
0
Views
622
Activity
Oct ’24
Binding with ForEach or List doesn't work anymore when using @Observable macro
Hi. The binding in a ForEach or List view doesn't work anymore when using the @Observable macro to create the observable object. For example, the following are the modifications I introduced to the Apple's example called "Migrating from the Observable Object Protocol to the Observable Macro" https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro struct LibraryView: View { @Environment(Library.self) private var library var body: some View { List($library.books) { $book in BookView(book: book) } } } All I did was to add the $ to turn the reference to library.books into a binding but I got the error "Cannot find '$library' in scope" Is this a bug or the procedure to use binding in lists changed? Thanks
Replies
3
Boosts
0
Views
2.4k
Activity
Oct ’24
Is @ObservationIgnored necessary for private var?
In SwiftUI's ViewModel class that are @Observable, is it necessary to annotate private fields as @ObservationIgnored? I'm not sure if adding @ObservationIgnored to these fields will get performance gains, since there are no SwiftUI structs referencing these fields because they're private. However, I'd like to know what's the recommended approach here? While this might not seem obvious for the example below, however, sometimes I have private fields that are changing pretty frequently. For these frequently changed fields, I think the performance gains will be larger. Example: @Observable class UserProfileViewModel { var userName: String? var userPhoneNumber: String? private var isFetchingData = false } vs @Observable class UserProfileViewModel { var userName: String? var userPhoneNumber: String? @ObservationIgnored private var isFetchingData = false }
Replies
2
Boosts
0
Views
2k
Activity
Oct ’24
Struggling with Swift Gesture/Observation
I'm trying to create an equivalent to TabView, but with the difference that the 2nd View slides in over the top of the primary view. Maybe there's a more elegant way of coding this (suggestions appreciated), but I've almost succeeded using the dragGesture. When a user swipes right to left the observed variable showTab2 is set to true, and the 2nd tab glides in over the top of tab 1 and displays 🥳. The only problem is, that when a user happens to start the swipe over a button, the observed status (showTab2) does change as expected but the main view does not catch this change and does not display tab2. And that despite the showTab2 being an @Observable. Any clues what I've missed? Or how to capture that the start of a swipe gesture starts over the top of a button and should be ignored. According to the code in SwipeTabView this screenshot 👆 should never occur. Here's the code: @Observable class myclass { var showTab2 = false } struct SwipeTabView: View { @State var myClass = myclass() @State var dragAmount: CGSize = CGSize.zero var body: some View { VStack { ZStack { GeometryReader { geometryProxy in VStack { tab(tabID: 1, selectedTab: myClass.showTab2) .zIndex(/*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) .background(.black) .transition(.identity) .swipeable(stateOfViewAdded: $myClass.showTab2, dragAmount: $dragAmount, geometryProxy: geometryProxy, insertion: true) } if myClass.showTab2 || dragAmount.width != 0 { tab(tabID: 2, selectedTab: myClass.showTab2) .zIndex(2.0) .drawingGroup() .transition(.move(edge: .trailing)) .offset(x: dragAmount.width ) .swipeable(stateOfViewAdded: $myClass.showTab2, dragAmount: $dragAmount, geometryProxy: geometryProxy, insertion: false) } } } } } } extension View { func swipeable(stateOfViewAdded: Binding<Bool>, dragAmount: Binding<CGSize>, geometryProxy: GeometryProxy, insertion: Bool) -> some View { self.gesture( DragGesture() .onChanged { gesture in // inserting must be minus, but removing must be positive - hence the multiplication. if gesture.translation.width * (insertion ? 1 : -1 ) < 0 { if insertion { dragAmount.wrappedValue.width = geometryProxy.size.width + gesture.translation.width } else { dragAmount.wrappedValue.width = gesture.translation.width } } } .onEnded { gesture in if abs(gesture.translation.width) > 100.0 && gesture.translation.width * (insertion ? 1 : -1 ) < 0 { withAnimation(.easeOut.speed(Double(gesture.velocity.width))) { stateOfViewAdded.wrappedValue = insertion } } else { withAnimation(.easeOut.speed(Double(gesture.velocity.width))) { stateOfViewAdded.wrappedValue = !insertion } } withAnimation(.smooth) { dragAmount.wrappedValue = CGSize.zero } } ) } } struct tab: View { var tabID: Int var selectedTab: Bool var body: some View { ZStack { Color(tabID == 1 ? .yellow : .orange) VStack { Text("Tab \(tabID) ").foregroundColor(.black) Button(action: { print("Tab2 should display - \(selectedTab.description)") }, label: { ZStack { circle label } }) Text("Tab2 should display - \(selectedTab.description)") } } } var circle: some View { Circle() .frame(width: 100, height: 100) .foregroundColor(.red) } var label: some View { Text("\(tabID == 1 ? ">>" : "<<")").font(.title).foregroundColor(.black) } }
Replies
4
Boosts
0
Views
818
Activity
Sep ’24
SwiftUI and SwiftData @Query
Hey everyone, I’m relatively new to SwiftUI and iOS development, having started earlier this year, and I’m working on a Notes app using SwiftData. I’ve run into some issues when dealing with nested views. In my app, I’m using a SwiftData @Query in a parent view and then passing the model objects down the view tree. However, I’m frequently encountering errors such as “No container found” or similar. To work around this, I’ve tried having the child views perform their own SwiftData @Query, with the parent view passing a string identifier to the child views. This approach has helped somewhat, but I’m still not entirely sure how to properly manage UI updates. Additionally, I’ve noticed that turning on iCloud syncing seems to have made the problem worse. The errors have become more frequent, and it’s unclear to me how to handle these situations effectively. Could someone explain the best practices for handling SwiftData queries in nested SwiftUI views, especially with iCloud syncing enabled? Any guidance or feedback would be greatly appreciated. Thank you!
Replies
1
Boosts
0
Views
1.5k
Activity
Aug ’24
How to observe SwiftData DB changes
How can one observe changes in the SwiftData DB? I'm aware that this is possible via Queries, but I need to fetch the data in background, so a query is not an option. I'm aware of the ModelContext.didSave / .willSave notifications, but these don't work with iOS 17. -> How can I observe changes to models of a certain type? I don't want to observe the whole database.
Replies
0
Boosts
3
Views
919
Activity
Aug ’24
SwiftUI State not reliable updating
Hello, I have a SwiftUI view with the following state variable: @State private var startDate: Date = Date() @State private var endDate: Date = Date() @State private var client: Client? = nil @State private var project: Project? = nil @State private var service: Service? = nil @State private var billable: Bool = false Client, Project, and Service are all SwiftData models. I have some view content that binds to these values, including Pickers for the client/project/service and a DatePicker for the Dates. I have an onAppear listener: .onAppear { switch state.mode { case .editing(let tt): Task { await MainActor.run { startDate = tt.startDate endDate = tt.endDate client = tt.client project = tt.project service = tt.service billable = tt.billable } } default: return } } This works as expected. However, if I remove the Task & MainActor.run, the values do not fully update. The DatePickers show the current date, the Pickers show a new value but tapping on them shows a nil default value. What is also extremely strange is that if tt.billable is true, then the view does update as expected. I am using Xcode 15.4 on iOS simulator 17.5. Any help would be appreciated.
Replies
1
Boosts
0
Views
758
Activity
Aug ’24
NavigationSplitView freezes
DESCRIPTION OF PROBLEM I have changed my app to the @Observable-Macro. When using an iPhone (on simulator and on real device) the navigation from a player to the player detail view and back breaks. In the attached video on my GitHub you can see me tapping on both players in the team, but the navigation ist not showing the detail view. What is the reason? Is my usage/understanding of @Observable wrong? Is it wrong to have the selectedPlayer within the PlayerController which is @Observable? And why does it sometimes work and sometimes not? The project can be found here: GitHub Project STEPS TO REPRODUCE Start the App, add one or two demo teams, tap on a team and add two or more demo players. tap a player and then go back, tap the player again and back again. After a while (number of taps is always different), the navigation breaks. See my video attached. PLATFORM AND VERSION iOS Development environment: Xcode 15.4, macOS 14.5 (23F79) Run-time configuration: iOS 17.5,
Replies
4
Boosts
0
Views
690
Activity
Aug ’24
@State ViewModel memory leak in iOS 17 (new Observable)
Our app has an architecture based on ViewModels. Currently, we are working on migrating from the ObservableObject protocol to the Observable macro (iOS 17+). The official docs about this are available here: https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro Our ViewModels that were previously annotated with @StateObject now use just @State, as recommended in the official docs. Some of our screens (a screen is a SwiftUI view with a corresponding ViewModel) are presented modally. We expect that after dismissing a SwiftUI view that was presented modally, its corresponding ViewModel, which is owned by this view (via the @State modifier), will be deinitialized. However, it seems there is a memory leak, as the ViewModel is not deinitialized after a modal view is dismissed. Here's a simple code where ModalView is presented modally (through the .sheet modifier), and ModalViewModel, which is a @State of ModalView, is never deinitialized. import SwiftUI import Observation @Observable final class ModalViewModel { init() { print("Simple ViewModel Inited") } deinit { print("Simple ViewModel Deinited") // never called } } struct ModalView: View { @State var viewModel: ModalViewModel = ModalViewModel() let closeButtonClosure: () -> Void var body: some View { ZStack { Color.yellow .ignoresSafeArea() Button("Close") { closeButtonClosure() } } } } struct ContentView: View { @State var presentSheet: Bool = false var body: some View { Button("Present sheet modally") { self.presentSheet = true } .sheet(isPresented: $presentSheet) { ModalView { self.presentSheet = false } } } } #Preview { ContentView() } Is this a bug in the iOS 17 beta version or intended behavior? Is it possible to build a relationship between the View and ViewModel in a way where the ViewModel will be deinitialized after the View is dismissed? Thank you in advance for the help.
Replies
4
Boosts
12
Views
3.4k
Activity
Jul ’24
Confusing SwiftUI error log: "Mutating observable property after view is torn down has no effect"
Hey, I have a setup in my app that I am currently building, that uses @Observable router objects that hold the app's entire navigation state, so that I can easily set it globally and let SwiftUI show the appropriate views accordingly. Each view gets passed in such a router object and there is a global "app" router that the app's root view can access as an entry point: @Observable @MainActor final class AppRouter { static let shared = AppRouter() // Entry point used by the app's root view var isShowingSheet = false // Navigation state let sheetRouter = SheetRouter() // Router that's passed to the sheet view. This router could contain other routers for sheets it will show, and so on } @Observable @MainActor final class SheetRouter { // Example of a "nested" router for a sheet view var path = NavigationPath() var isShowingOtherSheet = false func reset() { path = .init() isShowingOtherSheet = false } } To open a sheet, I have a button like this: @Bindable var appRouter = AppRouter.shared // ... Button("Present") { appRouter.sheetRouter.reset() // Reset sheet router appRouter.isShowingSheet = true // show sheet } This seems to work perfectly fine. However, this produces tons of "error" logs in the console, whenever I open the sheet for a second time: Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.isShowingOtherSheet after view is torn down has no effect. These errors appear when calling the reset() of the sheet view's router before opening the sheet. That method simply resets all navigation states in a router back to their defaults. It's as if the sheetRouter is still connected to the dismissed view from the previous sheet, causing a mutation to trigger these error logs. Am I misusing SwiftUI here or is that a bug? It's also worth mentioning that these error logs do not appear on iOS 17. Only on iOS 18. So it might be a bug but I just want to make sure my usage of these router objects is okay and not a misuse of the SwiftUI API that the runtime previously simply did not catch/notice. Then I'd have to rewrite my entire navigation logic. I do have an example project to demonstrate the issue. You can find it here: https://github.com/SwiftedMind/PresentationBugDemo. I have also filed a feedback for this: FB14162780 STEPS TO REPRODUCE Open the example project. Open the sheet in the ContentView twice by tapping "Present" Close that sheet Open it again. Then the console will show these error logs from above. I'd appreciate any help with this. Cheers
Replies
2
Boosts
2
Views
1.1k
Activity
Jul ’24
Using @Bindable with a Observable type
Originally asked on Swift Forums: https://forums.swift.org/t/using-bindable-with-a-observable-type/70993 I'm using SwiftUI environments in my app to hold a preferences object which is an @Observable object But I want to be able to inject different instances of the preferences object for previews vs the production code so I've abstracted my production object in to a Preferences protocol and updated my Environment key's type to: protocol Preferences { } @Observable final class MyPreferencesObject: Preferences { } @Observable final class MyPreviewsObject: Preferences { } // Environment key private struct PreferencesKey: EnvironmentKey { static let defaultValue : Preferences & Observable = MyPreferencesObject() } extension EnvironmentValues { var preferences: Preferences & Observable { get { self[PreferencesKey.self] } set { self[PreferencesKey.self] = newValue } } } The compiler is happy with this until I go to use @Bindable in my code where the compiler explodes with a generic error, eg: @Environment(\.preferences) private var preferences // ... code @Bindable var preferences = preferences If I change the environment object back to a conforming type eg: @Observable final class MyPreferencesObject() { } private struct PreferencesKey: EnvironmentKey { static let defaultValue : MyPreferencesObject = MyPreferencesObject() } extension EnvironmentValues { var preferences: MyPreferencesObject { get { self[PreferencesKey.self] } set { self[PreferencesKey.self] = newValue } } } Then @Bindable is happy again and things compile. Is this a known issue/limitation? Or am I missing something here?
Replies
3
Boosts
1
Views
1.1k
Activity
Jun ’24
[sample code]when model changed, the view in SwiftUI isn’t update
in the SampleTrips, for the pure SwiftData version, when I add a living accommodation from the trip detail view, the trip detail view won’t update until I go back to the trip list view and enter again. why didn’t the trip detail view update? I have tested the pure CoreData version in sample code, it worked well. is this a bug or a feature of SwiftData?
Replies
1
Boosts
0
Views
876
Activity
Jun ’24
SwiftUI - Using Bindable with Environment
At the moment, using Bindable for an object stored in Environment works in a cumbersome way: struct ContentView: View { @Environment(Model.self) var model var body: some View { @Bindable var model = model VStack { Text(model.someField.uppercased()) TextField("", text: $model.someField) someSubView } .padding() } @ViewBuilder var someSubView: some View { @Bindable var model = model TextField("", text: $model.someField) } } A new @Bindable needs to be instantiated for each computed property in the view, which creates boilerplate I would like to avoid. I made a new property wrapper which functions the same as the EnvironmentObject wrapper, but for Observable: @propertyWrapper struct EnvironmentObservable<Value: AnyObject & Observable>: DynamicProperty { @Environment var wrappedValue: Value public init(_ objectType: Value.Type) { _wrappedValue = .init(objectType) } public init() { _wrappedValue = .init(Value.self) } private var store: Bindable<Value>! var projectedValue: Bindable<Value> { store } mutating func update() { store = Bindable(wrappedValue) } } Example: struct ContentView: View { @EnvironmentObservable var model: Model var body: some View { VStack { Text(model.someField.uppercased()) SubView(value: $model.someField) someSubView } .padding() } var someSubView: some View { TextField("", text: $model.someField) } } I was wondering if there would be any downsides to using this method? In my testings it seems to behave the same, but I'm not sure if using this could have a performance impact.
Replies
1
Boosts
2
Views
1k
Activity
Jun ’24
I wonder swiftdata query cannot be used within a class
import SwiftUI import SwiftData class DateManagerStore : ObservableObject { @Query private var myData: [myData] @Published var myDataToString = "" func hopitalDataQuery() { if let lastMyData = myData { self.myDataToString = String(lastMyData.sorted(by: {$0.visitedDate > $1.visitedDate}).last) } } } struct MainView: View { @EnvironmentObject var dateManagerStore : DateManagerStore var body: some View { VStack{ Text("\(dateManagerStore.myDataToString)") } .onAppear(perform: { dateManagerStore.hopitalDataQuery() }) } } I thought it would be good to manage SwiftData values ​​used within multiple views in one place. I wanted to use Query data in the DateManagerStore class declared as ObservableObject through onApper of the MainView. However, when printing the myData variable within hopitalDataQuery() of the DateManagerStore class, empty data was output. I tried to use @Query defined inside the DateManagerStore class in various ways, but none of the methods allowed me to put a value into the @Query variable 'myData'. There is no error in Xcode itself, but no data is coming in. I can't find any related information anywhere, so I ask if it's officially not possible.
Replies
3
Boosts
2
Views
2k
Activity
May ’24
@Observable conflict with @Query
If I annotate a class with @Observable I get this error in @Query: Expansion of macro 'Query()' produced an unexpected 'init' accessor If I remove @Observable the error goes away. Elsewhere I have .environment referencing the class. With @Observable this complains that the class needs to be @Observable. I am mystified. Does anyone have a suggestion?
Replies
4
Boosts
0
Views
1.2k
Activity
May ’24
Alternative option to initial onChange callback for EmptyView
I am exploring on managing state in SwiftUI app with purpose built Views due to the advantages for managing dependency with Environment. This is the minimal example I came up with: @MainActor struct AsyncStateModifier<T: Equatable>: View { let input: T let action: (T) async -> Void @Environment var queue: AsyncActionQueue var body: some View { return EmptyView() .onChange(of: input, initial: true) { old, new in queue.process(action: action, with: input) } } } The drawback of this approach is initial: true allows the onChange callback to fire when view appears and since EmptyView doesn't appear the action is never executed initially. When replacing EmptyView with Rectangle().hidden() this can be achieved, but I wanted to avoid having any impact on view hierarchy and EmptyView is suitable for that. Is there any alternative approach to make something like this possible?
Replies
1
Boosts
0
Views
807
Activity
May ’24
Is it true that @State is used for Value types and @StateObject is used for Reference types?
I am little confused about when to use State / StateObject / ObservedObject. What I have researched and what I understand: @State --> for value types @StateObject --> for reference types @ObservedObject --> child objects who needs reference to above two (the parent object should have @State/@StateObject and the object should conform to Observable) I am clear about Environment object.
Replies
1
Boosts
0
Views
610
Activity
May ’24
Migrating @MainActor ViewModel to @Observable causing error
I get this error while migrating from ObservableObject to @Observable. Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context My original code: struct SomeView: View { @StateObject private var viewModel = ViewModel() } After migration: @MainActor @Observable class BaseViewModel { } @MainActor class ViewModel: BaseViewModel { } struct SomeView: View { @State private var viewModel = ViewModel() } As discussed here. It seems like @StateObject is adding @MainActor compliance to my View under the hood because it's wrappedValue and projectedValue properties are marked as @MainActor, while on @State they are not. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @frozen @propertyWrapper public struct StateObject&lt;ObjectType&gt; : DynamicProperty where ObjectType : ObservableObject { ... @MainActor public var wrappedValue: ObjectType { get } .... @MainActor public var projectedValue: ObservedObject&lt;ObjectType&gt;.Wrapper { get } } One solution for this is to mark my View explicitly as @MainActor struct ViewModel: View but it have it side effects, for example code like: Button(action: resendButtonAction) { Text(resendButtonAttributedTitle()) } Will result a warning Converting function value of type '@MainActor () -&gt; ()' to '() -&gt; Void' loses global actor 'MainActor' While could be easily solved by using instead Button(action: { resendButtonAction() } ) { Text(resendButtonAttributedTitle()) } I still feel like marking the whole View explicitly as @MainActor is not a good practice. Adding fake @StateObject property to my view also do the trick, but it's a hack (the same for @FetchRequest). Can anyone think of a more robust solution for this?
Replies
1
Boosts
1
Views
2.2k
Activity
May ’24