Observation

RSS for tag

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

Posts under Observation tag

38 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Why is ScrollView / LazyVStack retaining Views which causes memory leaks in the end?
Recently I noticed how my ViewModels aren't deallocating and they end up as a memory leaks. I found something similar in this thread but this is also happening without using @Observation. Check the source code below: class CellViewModel: Identifiable { let id = UUID() var color: Color = Color.red init() { print("init") } deinit { print("deinit") } } struct CellView: View { let viewModel: CellViewModel var body: some View { ZStack { Color(viewModel.color) Text(viewModel.id.uuidString) } } } @main struct LeakApp: App { @State var list = [CellViewModel]() var body: some Scene { WindowGroup { Button("Add") { list.append(CellViewModel()) } Button("Remove") { list = list.dropLast() } ScrollView { LazyVStack { ForEach(list) { model in CellView(viewModel: model) } } } } } } When I tap the Add button twice in the console I will see "init" message twice. So far so good. But then I click the Remove button twice and I don't see any "deinit" messages. I used the Debug Memory Graph in Xcode and it showed me that two CellViewModel objects are in the memory and they are owned by the CellView and some other objects that I don't know where are they coming from (I assume from SwiftUI internally). I tried using VStack instead of LazyVStack and that did worked a bit better but still not 100% "deinits" were in the Console. I tried using weak var struct CellView: View { weak var viewModel: CellViewModel? .... } but this also helped only partially. The only way to fully fix this is to have a separate class that holds the list of items and to use weak var viewModel: CellViewModel?. Something like this: class CellViewModel: Identifiable { let id = UUID() var color: Color = Color.red init() { print("init") } deinit { print("deinit") } } struct CellView: View { var viewModel: CellViewModel? var body: some View { ZStack { if let viewModel = viewModel { Color(viewModel.color) Text(viewModel.id.uuidString) } } } } @Observable class ListViewModel { var list = [CellViewModel]() func insert() { list.append(CellViewModel()) } func drop() { list = list.dropLast() } } @main struct LeakApp: App { @State var viewModel = ListViewModel() var body: some Scene { WindowGroup { Button("Add") { viewModel.insert() } Button("Remove") { viewModel.drop() } ScrollView { LazyVStack { ForEach(viewModel.list) { model in CellView(viewModel: model) } } } } } } But this won't work if I want to use @Bindable such as @Bindable var viewModel: CellViewModel? I don't understand why SwiftUI doesn't want to release the objects?
0
0
104
1w
Replacing array in Observed object won't update UI.
Hello, following is the issue: I have a @Observable view model which has an array of @Observable Items. Tapping an item leads to a detail like view to submit a value to the item. This work thru bindings. However I have the need to replace the contents of the array entirely with a fresh version loaded from the network. It will contain the "same" objects with the same id but some values might have changed. So replacing the entire array seems to not update the UI because the IDs are the same as before. Also this seems to break the bindings because when replacing the array, editing no longer updates the UI. How to test the behavior: Launch the app in simulator. Add some values to the items by tapping on an item and then on add. Notice how changes are updated. Tap the blue button to sync fresh data to the array. (Not replacing the actual array) Confirm everything is still working Replace the array with the red button. Editing and UI updates are broken from now on. What is the proper way to handle this scenario? Project: https://github.com/ChristianSchuster/DTS_DataReplaceExample.git
2
0
236
3w
SwiftUI not observing SwiftData changes
I have an app with the following model: @Model class TaskList { @Attribute(.unique) var name: String // Relationships var parentList: TaskList? @Relationship(deleteRule: .cascade, inverse: \TaskList.parentList) var taskLists: [TaskList]? init(name: String, parentTaskList: TaskList? = nil) { self.name = name self.parentList = parentTaskList self.taskLists = [] } } If I run the following test, I get the expected results - Parent has it's taskLists array updated to include the Child list created. I don't explicitly add the child to the parent array - the parentList relationship property on the child causes SwiftData to automatically perform the append into the parent array: @Test("TaskList with children with independent saves are in the database") func test_savingRootTaskIndependentOfChildren_SavesAllTaskLists() async throws { let modelContext = TestHelperUtility.createModelContext(useInMemory: false) let parentList = TaskList(name: "Parent") modelContext.insert(parentList) try modelContext.save() let childList = TaskList(name: "Child") childList.parentList = parentList modelContext.insert(childList) try modelContext.save() let fetchedResults = try modelContext.fetch(FetchDescriptor<TaskList>()) let fetchedParent = fetchedResults.first(where: { $0.name == "Parent"}) let fetchedChild = fetchedResults.first(where: { $0.name == "Child" }) #expect(fetchedResults.count == 2) #expect(fetchedParent?.taskLists.count == 1) #expect(fetchedChild?.parentList?.name == "Parent") #expect(fetchedChild?.parentList?.taskLists.count == 1) } I have a subsequent test that deletes the child and shows the parent array being updated accordingly. With this context in mind, I'm not seeing these relationship updates being observed within SwiftUI. This is an app that reproduces the issue. In this example, I am trying to move "Finance" from under the "Work" parent and into the "Home" list. I have a List that loops through a @Query var taskList: [TaskList] array. It creates a series of children views and passes the current TaskList element down into the view as a binding. When I perform the operation below the "Finance" element is removed from the "Work" item's taskLists array automatically and the view updates to show the removal within the List. In addition to that, the "Home" item also shows "Finance" within it's taskLists array - showing me that SwiftData is acting how it is supposed to - removed the record from one array and added it to the other. The View does not reflect this however. While the view does update and show "Finance" being removed from the "Work" list, it does not show the item being added to the "Home" list. If I kill the app and relaunch I can then see the "Finance" list within the "Home" list. From looking at the data in the debugger and in the database, I've confirmed that SwiftData is working as intended. SwiftUI however does not seem to observe the change. ToolbarItem { Button("Save") { list.name = viewModel.name list.parentList = viewModel.parentTaskList try! modelContext.save() dismiss() } } To troubleshoot this, I modified the above code so that I explicitly add the "Finance" list to the "Home" items taskLists array. ToolbarItem { Button("Save") { list.name = viewModel.name list.parentList = viewModel.parentTaskList if let newParent = viewModel.parentTaskList { // MARK: Bug - This resolves relationship not being reflected in the View newParent.taskLists?.append(list) } try! modelContext.save() dismiss() } } Why does my explicit append call solve for this? My original approach (not manually updating the arrays) works fine in every unit/integration test I run but I can't get SwiftUI to observe the array changes. Even more strange is that when I look at viewModel.parentTaskList.taskLists in this context, I can see that the list item already exists in it. So my code effectively tries to add it a second time, which SwiftData is smart enough to prevent from happening. When I do this though, SwiftUI observes a change in the array and the UI reflects the desired state. In addition to this, if I replace my custom list rows with an OutlineGroup this issue doesn't manifest itself. SwiftUI stays updated to match SwiftData when I remove my explicit array addition. I don't understand why my views, which is passing the TaskList all the way down the stack via Bindable is not updating while an OutlineGroup does. I have a complete reproducible ContentView file that demonstrates this as a Gist. I tried to provide the source here but it was to much for the post. One other anecdote. When I navigate to the TaskListEditorScreen and open the TaskListPickerScreen I get the following series of errors: error: the replacement path doesn't exist: "/var/folders/07/3px_03md30v9n105yh3rqzvw0000gn/T/swift-generated-sources/@_swiftmacro_09SwiftDataA22UIChangeDetectionIssue20TaskListPickerScreenV9taskLists33_A40669FFFCF66BB4EEA5302BB5ED59CELL5QueryfMa.swift" I saw another post regarding these and I'm wondering if my issue is related to this. So my question is, do I need to handle observation of SwiftData models containing arrays differently in my custom views? Why do bindings not observe changes made by SwiftData but they observe changes made explicitly by me?
1
0
274
Nov ’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) } } }
1
0
253
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
657
Oct ’24
@Observation: Best way of handling binding after injecting a View-Model
In WWDC 2023 there was a good summary of how to handle the iOS 17 Observation capability. But despite the clear graphics, it was still ambiguous (for me.) I want to inject a class (view-model) so that it can be used in the complete view heirarchy, and used in bindings to allow bi-directional communication. As far as I can tell there are 2 ways of declaring the VM (alternatives 1 and 2 in my code), and 2 ways of consuming the VM in a view (alternatives 3 and 4 in my code). Using the flow-diagram I can't determine which is best. Here's the crux of my #Observable problem. import SwiftUI // MARK: - Model struct MyMod { var title = "Hello, World!" } // MARK: - MVV @Observable class MyMVV { var model: MyMod init() { self.model = MyMod() } } // MARK: - App @main struct MyApp: App { @Bindable var myGlobalMVV = MyMVV() // Alternative 1 // @State var myGlobalMVV = MyMVV() // Alternative 2 var body: some Scene { WindowGroup { ContentView() .environment(myGlobalMVV) // inject } } } struct ContentView: View { var body: some View { ContentDeepHierarchyView() } } struct ContentDeepHierarchyView: View { @Environment(MyMVV.self) var myGlobalMVV // digest var body: some View { @Bindable var myLocalMVV = myGlobalMVV // Alternative 3 TextField("The new title", text: $myLocalMVV.model.title) // Alternative 3 TextField("The new title", text: Bindable(myGlobalMVV).model.title) // Alternative 4 } Opinions?
1
0
360
3w
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
269
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
644
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
428
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
603
Aug ’24
@Observable state class reinitializes every time view is updated
With the new @Observable macro, it looks like every time the struct of a view is reinitialized, any observable class marked as @State in the struct also gets reinitialized. Moreover, the result of the reinitialization immediately gets discarded. This is in contrast to @StateObject and ObservableObject, where the class would only be initialized at the first creation of the view. The initialization method of the class would never be called again between view updates. Is this a bug or an expected behavior? This redundant reinitialization causes performance issues when the init method of the observable class does anything slightly heavyweight. Feedback ID: FB13697724
2
1
595
Jul ’24
@Observable class not compatible with Codable?
So any time I create a class that's both @Observable and Codable, e.g. @Observable class GameLocationManager : Codable { I get a warning in the macro expansion code: @ObservationIgnored private let _$observationRegistrar = Observation.ObservationRegistrar() Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten. I've been ignoring them for now, but there are at least a half a dozen of them now in my (relatively small) codebase, and I'd like to find a solution (ideally one that doesn't require me to write init(decoder:) for every @Observable class in my project...), especially since I'm not sure what the actual consequences of ignoring this might be.
2
0
579
Nov ’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
459
Aug ’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
757
Jul ’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
511
Aug ’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
1.1k
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
556
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
449
May ’24
Trouble setting an optional @Observable object
i'm having trouble modifying an optional environment object. i'm using the .environment modifier to pass along an optional object to other views. to access it in other views, i have to get it through an @Environment property wrapper. but i can't modify it even if i redeclare it in the body as @Bindable. here's an example code: @main struct MyApp: App { @State private var mySession: MySession? var body: some Scene { HomeScreen() .environment(mySession) } } now for the HomeScreen: struct HomeScreen: View { @Environment(MySession.self) private var mySession: MySession? var body: some View { @Bindable var mySession = mySession Button { mySession = MySession() } label: { Text("Create Session") } } } an error shows up in the @Bindable declaration saying init(wrappedValue:)' is unavailable: The wrapped value must be an object that conforms to Observable. but MySession is declared as @Observable. in fact it works just fine if i don't make the environment optional, but i have to setup MySession in the root of the app, which goes against the app flow.
1
0
443
May ’24