View Descriptors

When building our apps, its fairly well known that passage models into our views leads to bad things. I don't want to say its a black and white POV but its rarely a good idea or even necessary to begin with.

An often recommended approach is to use a ViewModel or Extension to add properties that provide UI representations of our models data. I’d like to suggest a slight alternative that testable and scalable even across large projects.

Example

Lets say we have a simple type to represent a Product. We might have a Model similar to the following:

struct Product {
    let title: String
    let price: Decimal
}

A product can have a title and a price. We don’t want to lose our underlying types, but at some point we need to present the price to the user in their local currency.

View Model

One solution to this problem is to introduce a View Model.

struct ProductViewModel {
    let title: String
    let price: String
}

While this approach is highly testable and allows you to abstract away the presence of the original Model, it also comes with some caveats.

  1. The name of the ViewModel is tightly coupled to the original Model itself, and so refactoring could be painful to keep these names in sync. This is the #1 issue I’ve faced myself.
  2. Often leads to some duplication where the value can be mapped directly.

Swift Extension

Another solution is to simply extend the Model itself.

extension Product {
    var priceString: String? {
        let decimal = NSDecimalNumber(decimal: product.price)
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        return formatter.string(from: decimal)
    }
}

While this approach isn’t inherently bad – as long as you keep it immutable – one of the biggest issues arises when you start naming things. You can already see that we now have a price as well as a priceString variable to make it clear that the latter returns a string representation of the price.

Descriptors

An alternative approach I’ve come up with that I’ve battle tested and had proven success across even large projects, is something I call View Descriptors.

Its basically a merge of the above concepts. Where the Descriptor is a dedicated type that’s added to your model type through an extension.

extension Product {
    struct Descriptor {
        private let product: Product

        init(product: Product) {
            self.product = product
        }
    }

    var descriptor: Descriptor {
        return Descriptor(product: self)
    }
}

Now we have a dedicated type that can better describe our model to the UI. Lets add a currency specific price value to our descriptor.

extension Product.Descriptor {
    var price: String? {
        let decimal = NSDecimalNumber(decimal: product.price)
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        return formatter.string(from: decimal)
    }
}

We can now access our model’s values as such:

let product = Product(title: "iPhone X", price: 1000)
product.price
product.descriptor.price

// prints
// 1000
// £1,000.00

Conclusion

Compared to the approaches above, I feel this solution provides the best of both worlds, with almost none of the caveats.

  1. No need to sync type names (i.e. Product ‣ ProductVM)
  2. No unnecessary duplication
  3. API is much more descriptive – allowing variable name reuse
  4. Still highly testable API
  5. Really easy to migrate/refactor existing models in an incremental fashion

My favourite thing about this solution is that the model and the descriptors are implicitly tied together, while still providing unique interfaces for their specific domain.


If you liked this post or want to discuss more, leave a comment below or find me on Twitter