Over a million developers have joined DZone.

Design Patterns in Swift: Document-View (Part II)

In Part I, we discussed the motivation for the Document-View pattern, why it's useful, and when you should use it. Today, we're going to look at an example.

· Mobile Zone

In Part I, we discussed the motivation for the Document-View pattern, why it's useful, and when you should use it. Today, we're going to look at an example.

This is a simple example, but it shows how data can update from some backend store through the document and how the views can also update the data based on control gestures from the user. First, let’s define some protocols:

public protocol Document {
  typealias EventType
  typealias KeyType
  typealias DataType
  var state : DataType? { get set }
  func addView(key: KeyType, eventHandler: (EventType) -> Void)
  func removeView(key: KeyType)
}

public protocol Event {
  init(message: String)
  var message : String { get }
}


Here, we have two protocols, one defining a document, and one defining an Event. The document references multiple view callback functions while each view references a single document. Yay! Circular dependencies! Here, we get around this by manually adding each view to the document rather than supplying an initialization argument containing a list of views.

Next, using these protocols, let’s define some classes:

public class SimpleEvent : Event {
  public var message : String
  public required init(message: String) {
    self.message = message
  }
}


This is a simple event implementation. It packages a message into a simple event class. You’d frequently pass much more information than this in an event, perhaps even a pointer to a larger data object. In this case, we’re just passing a string to more clearly illustrate the pattern.

public final class StringDocument<T : Event> : Document {
  private var views : [String: (T) -> Void] = [:]
  private var data: String?
  public var state: String? {
    get {
      return data
    }
    set(state) {
      if let mystate = state {
        data = mystate
        if let mydata = data { notify(mydata) }
      }
    }
  }

  public func addView(key: String, eventHandler: (T) -> Void) {
      views[key] = eventHandler
  }

  public func removeView(key: String) {
      views.removeValueForKey(key)
  }

  public func removeAllViews() {
      views.removeAll()
  }

  private func notify(data: String) {
    let event = T(message: data)
    views.forEach { $1(event) }
  }

}    


The Document implementation manages the document data and a collection of views they're interested in updates to that data.

public final class StringView<T : Event, D: Document
  where D.EventType == T, D.DataType == String> {
  private let name : String = String(arc4random_uniform(10000))
  private var document : D
  private var data : String?

  init(document: D) {
  self.document = document
  }

  public func onDataChange(data: T) {
    self.data = data.message
    render()
  }

  public func submitData(data: String) {
  document.state = data
  }

  public func render() {
    if let mydata = data {
    print("(\(name)) data is: \(mydata)")
    } else {
    print("(\(name)) no data available")
    }
  }

}


Keep in mind, the View class is usually associated with some kind of visual user interface element. Here, for example, we haven’t implemented that kind of interactive control, though we have implemented the ability to change and render document data from a view, so the StringView class does behave like a view from the design pattern—though in a simple way. In the view class we can alter data, and when we do that, the document object is notified (and the document then notifies all the registered views). We also print the address of each view object in order to differentiate the objects when they’re printing submitted data. Really, very simple, but each class has the responsibilities outlined in the pattern. The document manages views and data from a backend repository of some kind. The view provides the ability to manage data and presents the data to the user when it changes. Both the document and view classes reference one another.

Here’s a simple example program using these classes:

typealias SimpleStringDocument = StringDocument<SimpleEvent>
typealias SimpleStringView = StringView<SimpleEvent, SimpleStringDocument>

var document = SimpleStringDocument()
var view0 = SimpleStringView(document: document)
var view1 = SimpleStringView(document: document)
var view2 = SimpleStringView(document: document)

document.addView(view0.name, eventHandler: view0.onDataChange)
document.addView(view1.name, eventHandler: view1.onDataChange)
document.addView(view2.name, eventHandler: view2.onDataChange)

[view0, view1, view2].forEach { $0.render() }

document.state = "blargh"

view0.submitData("foo")
view1.submitData("bar")

document.removeView(view2.name)

view0.submitData("blech")


Let’s run this example in a playground; what do we get?

(313) no data available

(4371) no data available

(460) no data available


After we create and add the new views, we render the document data from each view. The data has yet to be assigned, so we have no data to view.

(460) data is: blargh

(313) data is: blargh

(4371) data is: blargh

We’re setting information in the document from some backend repository, and the document then notifies all the views of the update. The connected views then print the data that has been updated in the repository, carried in the event message.

(0x00007f956d900210) new data is: foo

(0x00007f956d81eec0) new data is: foo

(0x00007f956bd037a0) new data is: foo

Here, we’re viewing data to the user based on notification events received in the views from the document.

(0x00007f956d900210) new data is: bar

(0x00007f956d81eec0) new data is: bar

(0x00007f956bd037a0) new data is: bar

Again with the changing of the data via a view object. Then, for fun, we remove one of the views.

(0x00007f956d900210) new data is: blech

(0x00007f956d81eec0) new data is: blech

Here, we only notify two views of the change, so we update the data twice, once through each view.

That’s it! Very simple, as I promised. Document-view patterns are fast to build, easy to test, but only really work in simple cases.


Click here for Part I of this article.

Topics:
patterns ,swift

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}