https://developer.apple.com/forums/thread/739163
iOS 17 introduced @Observable. that's an effective way to implement a stateful model object.
Seems there is no way to associate instances with views
However, we are not able to use @StateObject
as the model object does not have ObservableObject
protocol. An advantage of using @StateObject
is to make the object initialized once only for the view. It will keep going on until the view identifier is changed.
I put some examples. We have an 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 in that the view body uses the passed controller anyway.
Even passed a different controller, views use it.
Plus, in the case of initializing a controller takes expensive costs, which decreases performance.
so how do we manage this kind of use case?
A workaround - making a wrapper view that provides instances for views
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)
} else {
Color.clear
.onAppear {
assert(object == nil, "it should not be running twice or more.")
guard object == nil else { return }
object = _objectInitializer()
}
}
}
}
}
ObjectProvider(object: Controller() { controller in
MyView(controller: controller)
}
I hope it should be better ways rather than this workaround I made.
Updated 2023-12-21
import SwiftUI
@propertyWrapper
struct ObservableEdge<O: Observable>: DynamicProperty {
@State private var box: Box<O> = .init()
var wrappedValue: O {
if let value = box.value {
return value
} else {
box.value = factory()
return box.value!
}
}
private let factory: () -> O
init(wrappedValue factory: @escaping @autoclosure () -> O) {
self.factory = factory
}
private final class Box<Value> {
var value: Value?
}
}