SwiftUIにはDynamicProperty
というprotocolがある。
これを使ってみようという趣旨の記事を見かけた。
ので、私も試してみました。
@Now
import Combine import SwiftUI class Clock: ObservableObject { @Published private(set) var date: Date = Date() init() { Timer.publish(every: 1, on: .main, in: .default) .autoconnect() .assign(to: &$date) } } @propertyWrapper struct Now: DynamicProperty { @StateObject private var clock = Clock() var wrappedValue: Date { get { clock.date } } }
こういうのを作っておいて
import SwiftUI struct ContentView: View { static let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .none formatter.timeStyle = .long return formatter }() @Now private var date: Date var body: some View { Text(date, formatter: Self.dateFormatter) } }
こう使う。
これでContentView
は毎秒更新され、現在時刻を表示し続ける。
DynamicPropertyって何
ドキュメントには、Viewが再計算される前にDynamicProperty
のupdate
メソッドを呼んでくれるくらいの情報しかないが、実際にはもうちょっといろいろあるようだ。
上記の例でもわかるように、@ObservedObject
の更新によってViewが再描画される。
ここで急に、SwiftUIのCore Dataサポートのように、@FetchRequest
的な形で何かできるんじゃないか、と気づくと思う。
実際にRealmにはそういう機能があった。
現実のユースケース
DynamicProperty
では、State
なりStateObject
(またはObservedObject
)なり、あるいはEnvironment
(やEnvironmentObject
)を、Viewのpropertyで使えるようだ。
これは少しReact Hooksに似ている。ReactのuseState
とSwiftUIの@State
の対称性のように、ReactのCustom Hookとちょっと似ている。
とはいえ、@Now
の例は、単にObservableObject
をそのまま使えばいいわけで、独自のDynamicProperty
を作ることが正当化されるような場面は稀かも。