Simplifying Data Access in SwiftUI with @dynamicMemberLookup


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.