The Presentation Model

Preface: What Is and Isn’t MVC?

Stop me if you’ve heard this:

Our MVC Design

  • Model: We start with aMessageStore. It has a fetchAllConversations() method, that returns an array of Conversation objects. Each thread contains an array of Messageobjects.
  • View: A tableview with a MessageCell table view cell.
  • Controller: The datasource for the tableview.
class InboxViewController:UIViewController, UITableViewDataSource {
var conversations:[Conversation] = []
var messageStore:MessageStore!
var tableView:UITableView!
func conversationsDidUpdate(_ notification:Notification){
conversations = messageStore.fetchAllConversations()
tableView.reloadData()
}
override viewDidLoad(){
super.viewDidLoad()
// Set up subscribers, etc
}
}

Problem 1: Complex Display Rules

Let’s add a few rules around how data is displayed:

  • In that message preview, if the most recent message is a photo, display ‘Attachment: 1 Image.’ Otherwise, show the text of the message.
  • If the timestamp was today, show the time, like ‘10:00 am’. If it’s before today, show the day, like ‘Yesterday.’

Problem 2: Display State

Then there’s the search box. We need to keep track of the search string, but it’s a bad idea to treat UITextField's text property as the source of truth. For example, we might recycle views for performance reasons, and every iOS developer knows the self-inflicted pain of cell recycling bugs.

Problem 3: Testing

Let’s say you want to test message searching, to make sure it’s a case insensitive search. With business logic inside UIViewController, you need a large swatch of dependencies in your test harness.

Introducing the Presentation Model

As far back as 1988, Smalltalk developers realized there are really two types of models in the an app: the Domain Model, which deal with business logic, and the Application Model, which deals with how it’s represented on screen. The latter is bit ambiguous, so I prefer Martin Fowler’s 2004 rebranding, a Presentation Model.

class ConversationPresentation {
private var conversation:Conversation
init(_ conversation:Conversation){
self.conversation = conversation
}
var previewText:String {
guard let lastMessage = conversation.messages.last else {
return ""
}
if lastMessage.isPhoto {
return "Attachment: 1 Image"
} else {
return lastMessage.text
}
}
}
class InboxPresentation {
private var store:MessageStore
var conversations:[ConversationPresentation] = []
init(_ store:MessageStore){
self.store = store
self.refreshConversations()
self.subscribeToNotifications()
}
private func refreshConversations(){
self.conversations = store.conversations.map { conv in
return ConversationPresentation(conv)
}
postInboxNotification()
}
func messageStoreUpdated(_ notification:Notification){
self.refreshConversations()
}
}
var searchText:String? {
didSet {
refreshConversations()
}
}
private func refreshConversations(){
let filteredPresentations:Conversation
if let searchText = self.searchText {
filteredPresentations = store.conversations.filter { conv in
return conv.bodyContains(searchText)
}
} else {
filteredPresentations = store.conversations
}
self.conversations = filteredPresentations.map { conv in
ConversationPresentation(conv)
}
postInboxNotification()
}
func searchFieldDidUpdate(_ sender:Any){
self.inbox.searchText = self.searchField.text
}

What about MVVM?

If you read too many iOS blogs, by now you’re saying , “This sounds a lot like MVVM.” People keep saying MVVM, but I do not think it means what they think it means. If you’re interested in software anthropology, read on. Otherwise skip to ‘Trade Offs’ at the end.

Do these semantics matter?

“Does it matter that people say MVVM when they really mean presentation model? Even Apple says MVC when they really mean MVP.”

  • ViewModel’s overloaded meaning adds noise to Google results
  • Presentation describes the object’s responsibilities. See Managers
  • It’s a tiny thing, but but “Open Quickly” produces fewer results with “pres” than “view.”

The Trade Offs

It’s important to stress that a presentation model is not a new architecture. Don’t stick it in every project and say you practice MPVC.

class TaskPresentation {
var task:Task
init(_ task:Task){
self.task = task
}
var text:String {
return self.task.text
}
var important:Bool {
return self.task.important
}
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ben Sandofsky

Ben Sandofsky

6.3K Followers

Developer at Lux Optics. We build photography apps like Halide and Spectre.