Greetings, traveler!
In SwiftUI architecture, it’s common to separate data and presentation logic using a Model
, a ViewModel
, and a View
. The model holds your raw data, the view model prepares that data for display, and the view binds to the view model.
But this layering introduces a small inconvenience: deep property access. If your view model wraps a model, accessing data often ends up looking like this:
Text(viewModel.model.someText)
Repeatedly reaching into .model
not only adds visual noise, but also exposes internal structure that you may prefer to keep encapsulated.
This is where @dynamicMemberLookup
becomes useful.
A Cleaner Approach
Swift’s @dynamicMemberLookup
allows you to forward property access dynamically to another object. In our case, we can forward member access from the view model to the underlying model. Here’s what that might look like:
@dynamicMemberLookup
final class ViewModel {
private let model: Model
init(model: Model) {
self.model = model
}
subscript<T>(dynamicMember keyPath: KeyPath<Model, T>) -> T {
model[keyPath: keyPath]
}
}
Now, in your SwiftUI view, you can simply write:
struct DynamicMemberLookupView: View {
@State var viewModel = ViewModel(
model: Model(
someText: "Hello",
someNumber: 42
)
)
var body: some View {
VStack {
Text(viewModel.someText) // no `.model` needed
Text("\(viewModel.someNumber)")
}
}
}
This results in cleaner, more focused code. The view model still controls access to the model but does so more transparently.
However, if you try to create a bidirectional binding, you will encounter an error:
Cannot assign through dynamic lookup property: subscript is get-only
struct DynamicMemberLookupView: View {
@State var viewModel = ViewModel(
model: Model(
someText: "Hello",
someNumber: 42
)
)
var body: some View {
VStack {
TextField("", text: $viewModel.someText) // ❌ Error
}
}
}
How It Works
The @dynamicMemberLookup
attribute tells the compiler to redirect member access through a custom subscript. In this case, the subscript receives a key path into the Model
, and the view model simply returns the corresponding value.
This approach is type-safe and resolved at compile time, unlike some dynamic features in other languages. It works especially well when the view model is a thin wrapper over a model and you want to maintain encapsulation while simplifying access.
When to Use It
Use @dynamicMemberLookup
sparingly. It’s ideal when:
- You have a wrapper around a model and want to reduce boilerplate access.
- You want to avoid exposing internal structure unnecessarily.
- You can clearly document or control what members are accessible.
However, be cautious — overuse may reduce readability or make the code harder to navigate in larger teams.
Conclusion
@dynamicMemberLookup
is a subtle but powerful tool. Used well, it helps streamline your SwiftUI architecture by reducing clutter and improving the clarity of your views.
If you enjoyed this article, please feel free to follow me on my social media: