Post

Replies

Boosts

Views

Activity

Reply to @Observable observation outside of SwiftUI
You can use @Observable outside of SwiftUI with AsyncStream like this: struct CountingService { @Observable class Model { public var count: Int = 0 } let model = Model() static var shared = CountingService() init() { Task { [model] in let modelDidChange = AsyncStream { await withCheckedContinuation { continuation in let _ = withObservationTracking { model.count } onChange: { continuation.resume() } } } var iterator = modelDidChange.makeAsyncIterator() repeat { let x = model.count // do something } while await iterator.next() != nil } } } Or if you want the count in the stream, like this: let countDidChange = AsyncStream { await withCheckedContinuation { continuation in let _ = withObservationTracking { model.count } onChange: { continuation.resume() } } return model.count } for await count in countDidChange { } Inside of SwiftUI, @Observable is only designed for model data, for view data please use the View struct hierarchy with @State structs to model your view data and .task for async/await. You'll avoid consistency issues that way and can use many of the powerful features like environment and preferences which you would lose if you attempt to use classes for view data instead.
May ’24
Reply to @Observable does not conform with Equatable (and Hashable)
It's simple to provide conformance using ObjectIdentifier, e.g. import SwiftUI @Observable class ObservableContent: Hashable { var text1 = "Default" var text2 = "" static func == (lhs: ObservableContent, rhs: ObservableContent) -> Bool { lhs === rhs } func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } } struct ContentView: View { @State var observableContent: ObservableContent? var body: some View { Group { if let observableContent { NavigationStack { NavigationLink(value: observableContent) { Text("Navigation Link") } .navigationDestination(for: ObservableContent.self) { content in ObservableContentView(content: content) } } } } .onAppear { if observableContent == nil { observableContent = ObservableContent() } } .onDisappear { observableContent = nil } } } struct ObservableContentView: View { @Bindable var content: ObservableContent var body: some View { Form { TextField("Text1", text: $content.text1) Text(content.text1) } } }
Apr ’24
Reply to CLMonitor Add region after starting to monitor for event changes
I experienced the same bug and have reported it as FB13696956 You can find my report on openradar (the link is not allowed here for some reason?). Another issue I noticed is the for try await events does not throw a cancellation exception when the outer task is cancelled like normal streams do. Please submit your own feedback and reference my report so hopefully it will bring more attention to it so it can be fixed!
Mar ’24
Reply to CLMonitor Add region after starting to monitor for event changes
I'm experiencing the same bug with Xcode Version 15.3 and iPhone Simulator 17.4. When the app is launched monitoring events are received but are not received for any regions added while app already running. I noticed this sample (which is some very ropey SwiftUI by the way) shows the bug too so I will include that in my feedback report as the test harness: https://developer.apple.com/documentation/corelocation/monitoring_location_changes_with_core_location
Mar ’24
Reply to Heavy Duty Work With Async Await
You need to mark the func as nonisolated if you want a background thread, e.g. nonisolated func startWriteImagesNext() async -> Bool { Because you defined it in a ViewController it inherited the @MainActor which doesn't seem what you want to nonisolated bypasses that. Check the NSViewController/UIViewController header to see its @MainActor annotation. You could also declare the func outside of the ViewController to achieve the same effect, e.g. in a struct that is not marked as @MainActor, or a actor if you want some shared state between all the image tasks.
Feb ’24
Reply to iOS 16.1 Crashes when scroll (LazyVStack and LazyVGrid)
Can reproduce this with Xcode Version 15.1 beta 3 (15C5059c) deploying to iPhone 14 Pro running iOS 17.1.1. Make a test project with Apple's LazyVGrid sample code. Launch app in portrait, scroll to bottom, rotate to landscape, rotate to portrait, scroll to top, crash! let columns = [GridItem(.flexible()), GridItem(.flexible())] var body: some View { ScrollView { LazyVGrid(columns: columns) { ForEach(0x1f600...0x1f679, id: \.self) { value in Text(String(format: "%x", value)) Text(emoji(value)) .font(.largeTitle) } } } } private func emoji(_ value: Int) -> String { guard let scalar = UnicodeScalar(value) else { return "?" } return String(Character(scalar)) } id: \.self tut tut Apple sample code developer! (this is not the cause of this crash though).
Nov ’23
Reply to When SwiftUI View is Equatable - questions...
∙View can conform to Equatable... True, .equatable() is no longer required, not sure what version that changed in. . ∙When view contains @ObservedObject... False, regardless of the outcome of ==, body is always called when any @Published property of the object changes or objectWillChange is published. . ∙Does view, that is Equatable, need to care about equality of its subviews.. No, a subview will just follow the same algorithm. Best to only init a View with the data it actually needs and uses in its body to keep invalidation tight. . ∙When view returns true on equality check... True, if you returned true from == but had @Environment(\.calendar) var calendar and the calendar changed the body would still be called. This is the same behaviour as @ObservedObject in the 2nd question. . ∙When the observed object conforms to Equatable... In my testing the if an ObservedObject conformed to Equatable the == func was never called. My test code: import SwiftUI import Combine struct ViewTest { class MyObject: ObservableObject, Equatable { static func == (lhs: ViewTest.MyObject, rhs: ViewTest.MyObject) -> Bool { // not called let x = lhs.counter1 == rhs.counter1 && lhs.counter2 == rhs.counter2 return x } @Published var counter1 = 0 @Published var counter2 = 0 func inc() { counter1 += 1 } } struct ContentView: View { @State var counter = 0 @State var counter2 = 0 @StateObject var object = MyObject() var body: some View { Form { Button("Increment \(counter)") { counter += 1 } Button("Increment \(counter2)") { counter2 += 1 } // ContentView2(x: counter) { [c = counter] in // print("\(c)") // body is called once when counter2 is changed the first time (once after every time if it has the onReceive // } ContentView2(x: counter2) .environment(\.calendar, ((counter % 2 == 0) ? Calendar.current : Calendar(identifier: .chinese))) //{ // print("\(self.counter)") // body called every time counter2 is changed // } // .equatable() } } } struct ContentView2: View, Equatable { // @Environment(\.managedObjectContext) var context //@State var counter = 0 //@ObservedObject var object: MyObject @Environment(\.calendar) var calendar let x: Int // let y: () -> () // if the closure passed in does somethiung with self then body is always called. static func == (lhs: Self, rhs: Self) -> Bool { let result = lhs.x == rhs.x //&& lhs.object.counter2 == rhs.object.counter2 return result } var body: some View { let _ = Self._printChanges() HStack { Text("ff") Button("Button") { //y() } } // .onReceive(Just(0)) { _ in // causes body to be called // print("") // } // .task { // counter = 0 // }.onChange(of: x) { a in // // } // .onAppear { // counter = 0 // } } } }
Nov ’23