Streamlining lots of View Controller interactions with Swift enums.

What ‘problem’ am I trying to solve?

In most cases a View Controller shows one screen to a user, but the user can interact with the screen in multiple ways. Every one of these interactions is handled differently in code. This can be through delegation, target-action, notifications, callbacks, …

For example, take the Travelplanner screen of the Reisplanner Extra app I’m working on.

Overview actions on TravelPlanner screen
  1. A modal Location Picker is presented when the row is pressed
  2. From & To locations are switched when pressed
  3. A modal Location Picker is presented when the row is pressed
  4. A modal Date Picker is presented when the row is pressed
  5. The Now toggle button can be in an on/off/automatic state and changes the behavior of the time row
  6. A modal Travel Options screen is presented when the button is pressed
  7. 8. 9. 10. … more actions 🙂

As you can see, there are a lot of interactions (and there are actually even more interactions for different states of this screen), and all of these interactions need to be handled in code.
In most cases you’ll end up with all kinds of different methods handling these cases, but over time it can start to become unclear where to look when something needs to be changed or added inside your View Controller.
When a (new) team member has to work in the codebase that he has never touched before, it can take some time to find his/her way and know where to look. So how do I try to streamline this?
The enum for the TravelPlanner screen looks like this:

As you can see, there are a lot of interactions (and there are actually even more interactions for different states of this screen), and all of these interactions need to be handled in code.
In most cases you’ll end up with all kinds of different methods handling these cases, but over time it can start to become unclear where to look when something needs to be changed or added inside your View Controller.
When a (new) team member has to work in the codebase that he has never touched before, it can take some time to find his/her way and know where to look. So how do I try to streamline this?
The enum for the TravelPlanner screen looks like this:

Swift Enums, your best friend.

Every interaction on the screen, triggers something, and I model all these interactions in a Swift enum called PerformAction .

enum PerformAction {
  case fromLocationRowPressed( location:Location? )
  case toLocationRowPressed( location:Location? )
  case switchLocationButtonPressed
  case departureArrivalRowPressed( queryType:TravelTimeQueryType, date:Date )
  case nowButtonPressed
  case travelOptionsButtonPressed
  case widgetDirectTo_optionsButtonPressed
  case widgetDirectTo_configureAddressButtonPressed
  case widgetDirectTo_selectLocation( location:Location )
  case widgetCiCo_optionsButtonPressed
  case nothing
}
Swift

Implementation

This enum is added as a nested type inside the TravelPlannerViewController, so you can reuse the same PerformAction enum name across your files without getting name collisions.

UIViewController {
    enum PerformAction { ... }
}
Swift

If a new member starts reading through your codebase, he/she can check this enum and see at a glance what all the different actions are for this screen.

To react on these interactions, we create a property performedAction and add a didSet property observer.

var actionPerformed:PerformAction = .nothing {
    didSet {
        switch actionPerformed {
            
        case .fromLocationRowPressed( let location ):
            presentLocationPicker( location: location )
        case .toLocationRowPressed( let location ):
            presentLocationPicker( location: location )
        case .switchLocationButtonPressed:
            switchLocations()
        case .departureArrivalRowPressed( let queryType, let date ):
            presentDatePicker( queryType, date: date )
        
        ...
        }       
    }
}
Swift

Proxying

Now you have 1 point of entry to handle all your interactions and call your methods.

@IBAction func fromLocationPressed( _ sender: UIButton ) {    
  actionPerformed = .fromLocationRowPressed( location: fromLocationRow.location )    
}
@IBAction func switchButtonPressed( _ sender: UIButton ) {    
  actionPerformed = .switchLocationButtonPressed    
}
Swift

When using RxSwift I use a PublishSubject to keep track of changes.

// Create the subject to keep track of the performed actions
private let performedActionSubject = PublishSubject<PerformedAction>()
    
// Add a new action to the stream
performActionSubject.onNext( .travelOptionsButtonPressed )
    
// Get updates when an action has been performed
viewModel.outputs.actionPerformedObservable.subscribe(onNext: { [weak self] (performedAction) in    
    switch performedAction {    
        case ...    
    }
}).disposed( by: disposeBag )
Swift

Final thoughts

This is not a technique I use for a screen with only one or two actions. I tend to model it like this, if there are a lot of interactions.

The downside is you’ll get a bit of extra code, because you proxy your interaction handling to the PerformAction enum.

The benefits are

  1. See all the interactions in the enum at a glance
  2. Clarity of having one point where all the interactions are handled

For newcomers (or your future-you) it will be easier to get around the code.

Share this post

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts