StateViewController

A container view controller that manages the appearance of one or more child view controller for any given state.

Overview

This class is designed to make stateful view controller programming easier. Typically in iOS development, views representing multiple states are managed in one single view controller, leading to large view controller classes that quickly become hard to work with and overlook at a glance. For instance, a view controller may display an activity indicator while a network call is performed, leaving the view controller to have to directly manipulate view hierarhy for each state. Furthermore, the state of a view controller tends to be represented by conditions that are hard to synchronize, easily becoming a source of bugs and unexpected behavior. With StateViewController each state can be represented by one or more view controllers. This allows you to composite view controllers in self-contained classes resulting in smaller view controllers and better ability modularize your view controller code, with clear separation between states.

Subclassing notes

You must subclass StateViewController and define a state for the view controller you are creating.

enum MyViewControllerState {
    case loading
    case ready
}

Note: Your state must conform to Equatable in order for StateViewController to distinguish between states.

Override loadAppearanceState() to determine which state is being represented each time this view controller is appearing on screen. In this method is appropriate to query your model layer to determine whether data needed for a certain state is available or not.

override func loadAppearanceState() -> MyViewControllerState {
    if model.isDataAvailable {
        return .ready
    } else {
        return .loading
    }
}

To determine which content view controllers represent a particular state, you must override children(for:).

override func children(for state: MyViewControllerState) -> [UIViewController] {
    switch state {
    case .loading:
        return [ActivityIndicatorViewController()]
    case .empty:
        return [myChild]
    }
}

Callback methods are overridable, notifying you when a state transition is being performed, and what child view controllers are being presented as a result of a state transition.

Using willTransition(to:animated:) you should prepare view controller representing the state being transition to with the appropriate data.

override func willTransition(to state: MyViewControllerState, animated: Bool) {
    switch state {
    case .ready:
        myChild.content = myLoadedContent
    case .loading:
        break
    }
}

Overriding didTransition(to:animated:) is an appropriate place to invoke methods that eventually results in a state transition being requested using setNeedsTransition(to:animated:), as it ensures that any previous state transitions has been fully completed.

override func didTransition(from previousState: MyViewControllerState?, animated: Bool) {
    switch state {
    case .ready:
        break
    case .loading:
        model.loadData { result in
            self.myLoadedContent = result
            self.setNeedsTransition(to: .ready, animated: true)
        }
    }
}

You may also override loadChildContainerView() to provide a custom container view for your content view controllers, allowing you to manipulate the view hierarchy above and below the content view controller container view.

Animating state transitions

By default, no animations are performed between states. To enable animations, you have three options:

  • Set defaultStateTransitioningCoordinator
  • Override stateTransitionCoordinator(for:) in your StateViewController subclasses
  • Conform view controllers contained in StateViewController to StateViewControllerTransitioning.

Container view controller forwarding

State transitioning

  • Indicates whether the view controller currently is transitioning between states.

  • Indicates the current state, or invokes loadAppearanceState() is a current state transition has not yet began.

  • Indicates whether the state of this view controller has been determined. In effect, this means that if this value is true, you can access currentState inside

  • Loads a state that should represent this view controller immediately as this view controller is being presented on screen, and returns it.

    Warning

    As currentState may invoke use this method you cannot access currentState inside this method without first asserting that hasDeterminedState is true.

  • Notifies the state view controller that a new state is needed. As soon as the state view controller is ready to change state, a state transition will begin.

    Note

    Multiple calls to this method will result in the last state provided being transitioned to.

Content view controllers

  • Returns an array of content view controllers representing a state. The order of the view controllers matter – first in array will be placed first in the container views view hierarchy.

  • Container view placed directly in the StateViewControllers view. Content view controllers are placed inside this view, edge to edge.

    Important

    You should not directly manipulate the view hierarchy of this view
  • Note

    This method is only called once.

Callbacks

  • Notifies the view controller that a state transition is to be performed.

    Use this method to prepare view controller representing the given state for display.

  • Notifies the view controller that it has finished transitioning to a new state.

    As this method guarantees that a state transition has fully completed, this function is a good place to call setNeedsTransition(to:animated:), or methods that eventually (asynchronously or synchronously) calls that method.

  • Notifies the view controller that a content view controller will appear.

  • Notifies the view controller that a content view controller did appear.

    This method is well suited as a function to add targets and listeners that should only be present when the provided content view controller is on screen.

  • Notifies the view controller that a content view controller will disappear. This method is well suited as a fucntion to remove targets and listerners that should only be present when the content view controller is on screen.

  • Notifies the view controller that a content view controller did disappear.