Observation

RSS for tag

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

Posts under Observation tag

54 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

How to observe changes to properties of an @Observable from another non-view class?
I have a class A and class B, and both of them are @Observable, and none of them are a view. Class A has an instance of class B. I want to be able to listen to changes in this instance of class B, from my class A. Previously, by using ObservableObject, instead of the @Observable macro, every property that needed to be observable, had to be declared as a Publisher. With this new framework, everything seems to be automatically synthesised, and using a Publisher is no longer required, as all the properties are observable. That being said, how can I have an equivalent using @Observable? Is this new macro only for view-facing classes?
1
1
914
Sep ’23
Cannot use instance member 'golfData' within property initializer; property initializers run before 'self' is available ?
******* TestData.swift ************************ struct Measures: Identifiable { let id = UUID() var dataSeq: Int var value: Double } struct Items: Identifiable { let id = UUID() var name: String var measures: [Measures] } struct chartItemInfo: Identifiable { var id = UUID() var testItem: String var value: Double } var angleItem : [chartItemInfo]? var degreeItem : [chartItemInfo]? var grip1Item : [chartItemInfo]? var grip2Item : [chartItemInfo]? class TestData: ObservableObject { @Published var angleItem : [chartItemInfo]? @Published var degreeItem : [chartItemInfo]? @Published var grip1Item : [chartItemInfo]? @Published var grip2Item : [chartItemInfo]? } ******** CalculatorViewModel.swift ****************************************** class CalculatorViewModel : NSObject, ObservableObject, Identifiable { .... typealias test_Array = (time: String, swingNum: Int, dataSeqInSwing: Int, timeStampInSeq: Int, angle: Double, degree: Double, grip1: Double, grip2: Double) .... @Published var testDBdata = [test_Array]() @Published var chartDBdata = [chart_Array]() // @StateObject var testData : TestData var Testitems = [ (channel: "angle", data: testData.angleItem), (channel: "degree", data: testData.degreeItem), (channel: "grip1", data: testData.grip1Item), (channel: "grip2", data: testData.grip2Item)]. => Cannot use instance member 'testData' within property initializer; property initializers run before 'self' is available ? purpose of this project: read FMDB data and then make Array with DB data. and then i use these Array for displaying multi plot in Chart.
1
0
490
Oct ’23
iOS 17 @Obervable object - how to make it lazy to init once for the identical view?
iOS 17 introduced @Observable. that's an effective way to implement a stateful model object. however, we will not be able to use @StateObject as the model object does not have ObservableObject protocol. An advantage of using StateObject is able to make the object initializing once only for the view. it will keep going on until the view identifier is changed. I put some examples. We have a Observable implemented object. @Observable final class Controller { ... } then using like this struct MyView: View { let controller: Controller init(value: Value) { self.controller = .init(value: value) } init(controller: Controller) { self.controller = controller } } this case causes a problem that the view body uses the passed controller anyway. even passed different controller, uses it. and what if initializing a controller takes some costs, that decrease performance. how do we manage this kind of use case? anyway I made a utility view that provides an observable object lazily. public struct ObjectProvider<Object, Content: View>: View { @State private var object: Object? private let _objectInitializer: () -> Object private let _content: (Object) -> Content public init(object: @autoclosure @escaping () -> Object, @ViewBuilder content: @escaping (Object) -> Content) { self._objectInitializer = object self._content = content } public var body: some View { Group { if let object = object { _content(object) } } .onAppear { object = _objectInitializer() } } } ObjectProvider(object: Controller() { controller in MyView(controller: controller) } for now it's working correctly.
1
0
525
Oct ’23
@Environment for new @Observable macro not creating bindings?
Hi guys, I am trying to use the new @Observable macro. According to the documentation, this new macro will automatically generate observable properties, but I am having issues using my properties when Bindings are necessary. Previously I had a setup like this: class Example: ObservableObject { @Published public var myArray = [CustomType]() } I initialised one instance of the Example-class in @main @StateObject private var exampleClass = Example() And added as an .environmentObject onto the root view ContentView() .environmentObject(exampleClass) In child views I was able to access the @Published property as a binding via @EnvironmentObject private var example: Example $example.myArray What I am trying now This is the setup that I am trying with now: @Observable class Example { public var myArray = [CustomType]() } State instead of StateObject in @main @State private var exampleClass = Example() .environment instead of .environmentObject ContentView() .environmentObject(exampleClass) @Environment instead of @EnvironmentObject @Environment(Example.self) private var example This new setup is not letting me access $example.myArray. Is this intended? Could someone explain why?
1
1
704
Dec ’23
@Observable is being re-init every time view is modified
I am surprised at what I am seeing with the new @Observable framework in iOS 17. It seems that if the view containing the @State var, ie viewModel, is modified, then the init of that viewModel will also be called. This is not the behavior of the prior StateObject method of using a viewModel. As I understand the @State should not be reinitialized on redrawing on the View, is this the expected behavior? Example: struct ContentView: View { @State var offset = false var body: some View { VStack { InnerView() .offset(x: offset ? 200 : 0) Button("Offset") { offset.toggle() } } } } // iOS 17 Observable method: struct InnerView: View { @State var viewModel: ViewModel init() { self._viewModel = State(wrappedValue: ViewModel()) } var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Hello, world!") } .padding() } } @Observable class ViewModel { init() { print("ViewModel Init") } } // StateObject method: struct InnerView: View { @StateObject var viewModel: ViewModel init() { self._viewModel = StateObject(wrappedValue: ViewModel()) } var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Hello, world!") } .padding() } } class ViewModel: ObservableObject { init() { print("ViewModel Init") } }
2
1
752
Dec ’23
When I create an object, the view is updated, but when I update data inside that object, the view is not updated
class PostManager: ObservableObject { static let shared = PostManager() private init() {} @Published var containers: [PostContainer] = [] // Other code } class PostContainer: ObservableObject { var id: UUID = UUID() var timestamp: Date var subreddit: String var posts: [Post] var type: ContainerType var active: Bool // Init } @Model final class Post: Decodable, ObservableObject { // Other code } In my main view, a network request is made and a PostContainer is created if it doesn't exist. This code works fine, and the view is updated correctly. let container = PostContainer(timestamp: Date(), subreddit: subreddit, posts: posts, type: .search, active: true) self.containers.append(container) If the user wants to see more, they press a button and another request is made. This time, the new data will be added to the PostContainer instead of creating a new one. if let container = self.containers.first(where: {$0.subreddit == subreddit}) { // Update previous container container.timestamp = Date() print(container.posts.count) // Map IDs from container and then remove duplicates let existingIDs = Set(container.posts.map { $0.id }) let filtered = posts.filter { !existingIDs.contains($0.id) } // Append new post container.posts.append(contentsOf: filtered) container.active = true } This code is working fine as well, except for the view is not updating. In the view, PostManager is an @EnvironmentObject. I also have a computed variable to get the post and sort them. I added a print statement to that variable and saw that it wasn't being printed even though more data was being added to the PostContainer. At one point, I created an ID for the List that displays the data and had the code inside PostManager update that ID when it was finished. This of course worked, but it's not ideal. How can I get the view to update when post are appended inside of PostContainer?
1
0
372
Nov ’23
SwiftUI: Grandparent View Not Updating on Model Change in SwiftData
I'm working on a SwiftUI app using SwiftData for state management. I've encountered an issue with view updates when mutating a model. Here's a minimal example to illustrate the problem: import SwiftUI import SwiftData // MARK: - Models @Model class A { @Relationship var bs: [B] = [B]() init(bs: [B]) { self.bs = bs } } @Model class B { @Relationship var cs: [C] = [C]() init(cs: [C]) { self.cs = cs } } @Model class C { var done: Bool init(done: Bool) { self.done = done } } // MARK: - Views struct CView: View { var c: C var body: some View { @Bindable var c = c HStack { Toggle(isOn: $c.done, label: { Text("Done") }) } } } struct BView: View { var b: B var body: some View { List(b.cs) { c in CView(c: c) } } } struct AView: View { var a: A var body: some View { List(a.bs) { b in NavigationLink { BView(b: b) } label: { Text("B \(b.cs.allSatisfy({ $0.done }).description)") } } } } struct ContentView: View { @Query private var aList: [A] var body: some View { NavigationStack { List(aList) { a in NavigationLink { AView(a: a) } label: { Text("A \(a.bs.allSatisfy({ $0.cs.allSatisfy({ $0.done }) }).description)") } } } } } @main struct Minimal: App { var body: some Scene { WindowGroup { ContentView() } } } #Preview { let config = ModelConfiguration(isStoredInMemoryOnly: true) let container = try! ModelContainer(for: A.self, configurations: config) var c = C(done: false) container.mainContext.insert(c) var b = B(cs: [c]) container.mainContext.insert(b) var a = A(bs: [b]) container.mainContext.insert(a) return ContentView() .modelContainer(container) } In this setup, I have a CView where I toggle the state of a model C. After toggling C and navigating back, the grandparent view AView does not reflect the updated state (it still shows false instead of true). However, if I navigate back to the root ContentView and then go to AView, the status is updated correctly. Why doesn't AView update immediately after mutating C in CView, but updates correctly when navigating back to the root ContentView? I expected the grandparent view to reflect the changes immediately as per SwiftData's observation mechanism.
1
1
494
Nov ’23
Bindable is never deallocated
I've encountered a problem which I don't know if is related to swiftui, the observation framework, or both If I run the following code I have two tabs, with the second tab that use a "lazy model" which is deallocated each time disappears (this is necessary for my use case) If I switch to the second tab, all works right. If I return to the first tab, the onDisappear on the foo view should force the "Bar" variable to nil , because the FooView may be still allocated (it is a tab bar) but that resource should be released If that bar variable is set to nil, the MyBar should be replaced by the ProgressView in the "background" I expect regarding that the Bar that: the instance is nil on Foo no other view should be shown with that instance (MyBar is now disappeared) Because no ref, the Bar observable object should be now deallocated In reality the Bar object is still in my memory graph Any suggestions? is it a bug? @Observable class Bar { var hello: String = "" } struct Foo: View { @State var bar: Bar? @ViewBuilder private var content: some View { if let bar { MyBar(bar: bar) } else { ProgressView() } } var body: some View { content .onAppear { bar = Bar() } .onDisappear { self.bar = nil } } } struct MyBar: View { @Bindable var bar: Bar var body: some View { Text("MyBar") } } struct ContentView: View { @State var tag: Int = 0 var body: some View { TabView(selection: $tag) { Text("First") .tag(0) .tabItem { Text("First") } Foo() .tag(1) .tabItem { Text("Foo") } } } }
1
0
510
Nov ’23
.visualEffect with @Observable works only once
In my test app I have two squares (red and green) that change their size when I click a toggle button. I use two different approaches to change the scale. The red square is scaled using the .visualEffect modifier and the green square is scaled using the .scaleEffect modifier. When I click the button the first time, both squares change size. But when I click again and again, only the green square changes its size. The red square stays the same after the first change. Self._printChanges() show that both views get changes each time. What am I doing wrong? Or is this a bug? import SwiftUI import Observation struct ContentView: View { @State private var model: Model = Model() var body: some View { VStack { HStack { RedSquareView() GreenSquareView() } .environment(model) Button { model.scaled.toggle() } label: { Text("Toggle scale") } } .padding() } } struct RedSquareView: View { @Environment(Model.self) var model var body: some View { let _ = Self._printChanges() Rectangle() .fill(Color.red) .frame(width: 100, height: 100) .visualEffect { content, geometryProxy in content.scaleEffect(model.scaled ? 0.5 : 1.0, anchor: .center) } .animation(.bouncy, value: model.scaled) } } struct GreenSquareView: View { @Environment(Model.self) var model var body: some View { let _ = Self._printChanges() Rectangle() .fill(Color.green) .frame(width: 100, height: 100) .scaleEffect(model.scaled ? 0.5 : 1.0, anchor: .center) .animation(.bouncy, value: model.scaled) } } @Observable final class Model: Sendable { var scaled: Bool = false } #Preview { ContentView() }
0
0
313
Nov ’23
AVPlayer observePlayingState with @ObservationTracked possible?
I am following this Apple Article on how to setup an AVPlayer. The only difference is I am using @Observable instead of an ObservableObject with @Published vars. Using @Observable results in the following error: Cannot find '$isPlaying' in scope If I remove the "$" symbol I get a bit more insight: Cannot convert value of type 'Bool' to expected argument type 'Published<Bool>.Publisher' If I change by class to an OO, it works fine, although, is there anyway to get this to work with @Observable?
1
0
439
Nov ’23
AVFoundation player.publisher().assign() for @Observable/@ObservationTracked?
Following this Apple Article, I copied their code over for observePlayingState(). The only difference I am using @Observable instead of ObservableObject and @Published for var isPlaying. We get a bit more insight after removing the $ symbol, leading to a more telling error of: Cannot convert value of type 'Bool' to expected argument type 'Published.Publisher' Is there anyway to get this working with @Observable?
2
0
562
Nov ’23
No Observable Object of type...found
I am learning SwiftUI. Error: SwiftUI/Environment+Objects.swift:32: Fatal error: No Observable object of type ViewModel found. A View.environmentObject(_:) for ViewModel may be missing as an ancestor of this view. I do not get the point why this happens. The error is shown in the line : "renderer.render {... import SwiftData import SwiftUI @Observable class ViewModel { var groupNumber: String = "Gruppe" var sumOfHours: Int = 0 var personsToPrint: [Person]? = nil @MainActor func pdfReport(person: [Person]) { if personsToPrint != nil { for person in personsToPrint! { let renderer = ImageRenderer(content: PersonDetailView(person: person)) let url = URL.documentsDirectory.appending(path: "\(person.lastname) \(person.firstname).pdf") print(person.lastname, person.firstname) renderer.render { size, context in var box = CGRect(x: 0, y: 0, width: size.width, height: size.height) guard let pdf = CGContext(url as CFURL, mediaBox: &box, nil) else { return } pdf.beginPDFPage(nil) context(pdf) pdf.endPDFPage() pdf.closePDF() let data = try! Data(contentsOf: url) //pdf muss erst in Daten umgewandelt werden. do { try data.write(to: url) print("Daten wurden hier gespeichert: \(url)") } catch { print("PDF could not be saved!") } } } } } }
1
0
580
Dec ’23
Can't use @observable
after i import swiftui, there's no @observable?? it shows unknown attribute... and i cant import observation or swiftdata import Foundation import SwiftUI @Observable class BusinessModel{ var businesses = [Business]() var selectedBusiness: Business? var input: String = "" var service = dataservice() }
2
0
616
Dec ’23
Observable / State memory leak on view updates
I am working on a app that uses the new Observation framework and MVVM design pattern. I have a view composed of a list and a header that shows statistics about the displayed content by using an Observable ViewModel. When I add new contents, it is added to the list but the header seems to not take it into account even if its init method and body property are called. @Observable final class ViewModel { let contents: [String] init(contents: [String]) { self.contents = contents print("ViewModel init") } deinit { print("ViewModel deinit") } } struct ContentHeaderView: View { @State private var viewModel: ViewModel init(contents: [String]) { self._viewModel = State( initialValue: ViewModel(contents: contents) ) } var body: some View { Text("\(self.viewModel.contents.count)") } } struct ContentListView: View { @State private var contents = ["Content 1"] var body: some View { NavigationStack { VStack { ContentHeaderView(contents: self.contents) List(self.contents, id: \.self) { content in Text(content) } } .toolbar { ToolbarItem { Button("Add Content") { let newContent = "New Content \(self.contents.count + 1)" self.contents.append(newContent) } } } } } } In this example, when I tap the "Add Content" toolbar button, the header view that's supposed to show the number of content still display "1" even if the array now contains 2 elements. I added print statements to show that a new ViewModel is created every time the view is updated and the currently displayed view still uses the first created ViewModel which in fact contains a array with only one element. ContentHeaderView Init // on appear ContentHeaderView Init // First button tap // Every other tap ContentHeaderView Init ContentHeaderView Deinit I've read the threads 736239 and 736110 that discusses about similar issues when presenting sheets. I have similar code without issues since it's been fixed in iOS 17.2 beta 1 but it seems to still happen on view updates. Can you please confirm that this is an issue with the Observable framework and State properties or is there something I'm doing wrong ? Thanks
0
0
508
Dec ’23
How to address elements in an EnvironmentObject Array
I am new to programing apps, and I am trying to figure out how to use one item out of an array of items in a Text line. I am not getting any complaints from Xcode, but then the preview crashes giving me a huge error report that it keeps sending to Apple. I have cut out all the extra stuff from the app to get just the basics. What I want it to print on the screed is "Hello Bill How are you?" with Bill being from the observable array. The first picture below is about 2 seconds after I removed the // from in front of the line that reads Text("(friend2.friend2[1].name)"). The other two pictures are the main app page and the page where I setup the array. At the very bottom is a text file of the huge report it kept sending to Apple, until I turned of the option of sending to Apple. Would someone please explain what I am doing wrong. Well a side from probably everything. Error code.txt
1
0
522
Dec ’23
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
1.2k
May ’24
Previews crashing when using @Environment property wrapper
I am using the Observable macro and when I use @Environment property wrapper to instance my model the preview stop working. Sample code below my model Library import SwiftUI import Observation @Observable class Library { // ... } Now in my main app I created an instance of Library and add that instance to the environment @main struct BookReaderApp: App { @State private var library = Library() var body: some Scene { WindowGroup { LibraryView() .environment(library) } } } Now if I want to retrieve the Library instance from any view using the @Environment property wrapper the preview stop working completely struct LibraryView: View { @Environment(Library.self) private var library var body: some View { List(library.books) { book in BookView(book: book) } } } #Preview { LibraryView() } Check the 2 screenshots below Any idea why this is happening? Is there any workaround? I am working with Xcode Version 15.2. Thanks in advance for any kind of help!
1
0
470
Jan ’24
@Observable observation outside of SwiftUI
I've just started tinkering with @Observable and have run into a question... What is the best practice for observing an Observable object outside of SwiftUI? For example: @Observable class CountingService { public var count: Int = 0 } @Observable class ObservableViewModel { public var service: CountingService init(service: CountingService) { self.service = service // how to bind to value changes on service? } // suggestion I've seen that doesn't smell right func checkCount() { _ = withObservationTracking { service.count } onChange: { DispatchQueue.main.async { print("count: \(service.count)") checkCount() } } } } Historically using ObservableObject I'd have used Combine to monitor changes to service. That doesn't seem possible with @Observable and I don't know that I've come across an accepted / elegant solution? Perhaps there isn't one? There's no particular reason that CountingService has to be @Observable -- it's just nice and clean. Any suggestions would be appreciated!
4
1
1.2k
May ’24