For the last few years, almost all of the Flex projects I've worked on have used the Presentation Model (PM) pattern along with the Parsley application framework for dependency injection. 

Broadly speaking, a presentation model holds the presentational state and behavioural logic of an application and, in doing so, minimises the amount of code in view controls. Because several views can utilise the same presentation model, properties such as state can be shared between them.

Views hold a strongly typed reference to the presentation model so they can, for example, invoke methods on it. The presentation model itself has no knowledge of the views and, in the case of Flex and Parsley, it dispatches events or messages when something has changed.

For example, in a typical master-detail application, rather than the master list passing its selected item directly to the detail view when the user selects an item, it would invoke a method on its PM informing it of the change. The PM would then dispatch an event that would be picked up by the detail view to display the full record in the UI.

I thought I'd spend my time implementing a similar pattern in my recent node based UIScrollView demonstration.

The first step is to create NodesPM - in the absence of a dependency injection framework, I've cheated a little by making this struct entirely static. Because my little app only has one PM at the application level, IMHO, it's a reasonable strategy. For larger applications, a different approach may be required. 

My PM holds the application's state with the variables selectedNode and isDragging and exposes some methods for node management, createNewNode() and moveSelectedNode().

In the place of Parsley messaging, I'm using NSNotificationCentre to broadcast events. So, when a user performs a long hold gesture to create a new node, the relevant view invokes createNewNode():


    let gestureLocation = recognizer.locationInView(self)
    [...]
    NodesPM.createNewNode(gestureLocation)

...the PM then creates a node value object and dispatches a message with that object as a payload:


    static func createNewNode(origin: CGPoint)
    {
        let newNode = NodeVO(name: "\(nodes.count)", position: origin)
        
        nodes.append(newNode)
        
        let notification = NSNotification(name: NodeNotificationTypes.NodeCreated.toRaw(), object: newNode)
        
        notificationCentre.postNotification(notification)
        
        selectedNode = newNode

    }

Back in the view, during initialisation I've added an observer on the PM's NodeCreated notification:


    NodesPM.notificationCentre.addObserver(self, selector: "nodeCreated:", name: NodeNotificationTypes.NodeCreated.toRaw(), object: nil)

...so that when the view receives that event, it knows to create the relevant display object to reflect the change in the domain model:


    func nodeCreated(value : AnyObject)
    {
        let newNode = value.object as NodeVO
        
        let originX = Int( newNode.position.x - 75 )
        let originY = Int( newNode.position.y - 75 )
        
        let nodeWidget = NodeWidget(frame: CGRect(x: originX, y: originY, width: 150, height: 150), node: newNode)
        
        addSubview(nodeWidget)

    }

The individual nodes update their colour so that the selected node is yellow and the unselected nodes are blue. This follows a similar workflow: when the user touches a node, I set the selectedNode property on the PM from the view:


    NodesPM.selectedNode = node

...and using a didSet observer, the PM posts a notification:


    static var selectedNode: NodeVO? = nil
    {
        didSet
        {
            if let node = selectedNode
            {
                let notification = NSNotification(name: NodeNotificationTypes.NodeSelected.toRaw(), object: selectedNode)
            
                notificationCentre.postNotification(notification)
            }
        }

    }

...and back in the widget, it compares the PM's selected node's value object with its own and updates itself accordingly:


    func nodeSelected(value : AnyObject)
    {
        let selectedNode = value.object as NodeVO
        
        backgroundColor = selectedNode == node ? UIColor.yellowColor() : UIColor.blueColor()
        label.textColor = selectedNode == node ? UIColor.blackColor() : UIColor.whiteColor()

    }

For me, the Presentation Model pattern is quick to implement and scales up really well. This prototype version could benefit from abstracting the notification centre code and maybe a better solution that a single static PM, but I think it's a great starting point to building Swift applications with multiple view components that share a single data source.

All the source code is available in my Git Hub repository.

Stop Press!

I've changed the NodesPM to make its notificationCentre private and added a new method to simplify adding observers:

    static func addObserver(observer: AnyObject, selector: Selector, notificationType: NodeNotificationTypes)
    {
        notificationCentre.addObserver(observer, selector: selector, name: notificationType.toRaw(), object: nil)

    }

Observers now don't know how notifications are implemented (so a change in implementation doesn't require any changes to views) and don't need to convert NodeNotificationTypes enumerations to their raw value. The old code, which looked like this:

    NodesPM.notificationCentre.addObserver(self, selector: "nodeCreated:", name: NodeNotificationTypes.NodeCreated.toRaw(), object: nil)

Now looks like this:

    NodesPM.addObserver(self, selector: "nodeCreated:", notificationType: .NodeCreated)

Additionally, I've created a helper function inside the PM to eliminate all the copied boilerplate code when posting notifications. The previous code looked like this:

    let notification = NSNotification(name: NodeNotificationTypes.NodeCreated.toRaw(), object: newNode)
        
    notificationCentre.postNotification(notification)

...and the new code looks like this:

    postNotification(.NodeCreated, payload: newNode)

All these changes have been pushed to my Git Hub repository.



0

Add a comment

About Me
About Me
Labels
Labels
Blog Archive
Loading