Screen

public protocol Screen : CustomStringConvertible

A protocol for a factory that builds a screen module and returns its container.

This protocol provides a universal interface for building a screen module, independent of its architectural pattern: MVC, VIPER, MVVM, etc.

Since the factory is the external entry point to the screen, the factory is called the Screen for convenience.

External Parameters

Most screens require some external parameters for their configuration. All such parameters should be declared as properties of the implementation of this protocol.

For example, a chat screen requires a chat ID:

class ChatViewController: UIViewController {

    let chatID: Int

    init(chatID: Int) {
        self.chatID = chatID

        super.init(nibName: nil, bundle: nil)
    }
}

Then the implementation of its factory might look like this:

struct ChatScreen: Screen {

    let chatID: Int

    func build(navigator: ScreenNavigator) -> UIViewController {
        ChatViewController(chatID: chatID)
    }
}

To create this chat screen, you will need to pass the ID to the initializer:

let chatScreen = ChatScreen(chatID: 123)

navigator.navigate(fromTop: .stack) { route in
    route.push(chatScreen)
}

Screen Key

Each screen has the name and traits properties, which are used to create a screen key. You can use this key to create a container that can be found in the hierarchy. Just conform your view controller to the ScreenKeyedContainer protocol and initialize it with this key. See the ScreenKeyedContainer protocol for an example of this approach.

Default implementation of the name property returns type name:

let chatScreen = ChatScreen(chatID: 123)

print(chatScreen.name) // Prints: "ChatScreen"

Usually you don’t need to implement the name property yourself. If you want to distinguish between screens of the same type, implement the traitsproperty. For example, chat screens may be different if they have different chat IDs. In this case, the traits property must include the chat ID:

struct ChatScreen: Screen {

    let chatID: Int

    var traits: Set<AnyHashable> {
        [chatID]
    }

    func build(navigator: ScreenNavigator) -> UIViewController {
        ChatViewController(chatID: chatID)
    }
}

Then the keys for 2 screens with different chat ID will not be equal:

let firstScreen = ChatScreen(chatID: 1)
let secondScreen = ChatScreen(chatID: 2)

print(firstScreen.key == secondScreen.key) // Prints: "false"

Simple Screens

If your screen consists only of a view controller, implementing a factory can be an extra complication. In this case, you can conform your view controller to the Screen protocol:

class SimpleViewController: UIViewController, Screen { }

The default implementation will return self in the build(navigator:) method, and an instance of this controller can be used as a factory in navigation:

navigator.navigate(fromTop: .stack) { route in
    route.push(SimpleViewController())
}

See also

ScreenKey

See also

ScreenContainer

See also

AnyScreen
  • A type of container that the screen uses for navigation.

    See also

    ScreenContainer

    Declaration

    Swift

    associatedtype Container : ScreenContainer
  • name Default implementation

    Screen name.

    Default implementation returns type name.

    Default Implementation

    Declaration

    Swift

    var name: String { get }
  • traits Default implementation

    Screen traits that are used to distinguish screens with the same name.

    Default implementation returns empty set.

    Default Implementation

    Declaration

    Swift

    var traits: Set<AnyHashable> { get }
  • Builds the screen module and returns its container.

    Declaration

    Swift

    func build(navigator: ScreenNavigator) -> Container

    Parameters

    navigator

    The navigator instance that the screen should use for its own navigation.

    Return Value

    Container instance.

  • eraseToAnyScreen() Extension method

    Wraps this screen with a type eraser.

    Use eraseToAnyScreen() to expose an instance of AnyScreen, rather than this screen’s actual type. This form of type erasure preserves abstraction across API boundaries, such as different modules. When you expose your screens as the AnyScreen type, you can change the underlying implementation over time without affecting existing clients.

    See also

    AnyScreen

    Declaration

    Swift

    public func eraseToAnyScreen() -> AnyScreen<Container>

    Return Value

    An AnyScreen wrapping this screen.

  • decorated(by:) Extension method

    Undocumented

    Declaration

    Swift

    public func decorated<Decorator: ScreenDecorator>(
        by decorator: Decorator
    ) -> AnyScreen<Decorator.Output> where Container == Decorator.Container
  • description Extension method

    Declaration

    Swift

    public var description: String { get }
  • key Extension method

    Screen key that can be used to search for a container in the container hierarchy.

    This key can be used to create a container of the ScreenKeyedContainer type. Default implementation creates key using the values of the name and key properties.

    Declaration

    Swift

    public var key: ScreenKey { get }

Available where Container: UIViewController

  • eraseToAnyModalScreen() Extension method

    Wraps this screen with a type eraser.

    Use eraseToAnyModalScreen() to expose an instance of AnyModalScreen, rather than this screen’s actual type. This form of type erasure preserves abstraction across API boundaries, such as different modules. When you expose your screens as the AnyModalScreen type, you can change the underlying implementation over time without affecting existing clients.

    See also

    AnyModalScreen

    See also

    AnyScreen

    Declaration

    Swift

    public func eraseToAnyModalScreen() -> AnyModalScreen

    Return Value

    An AnyModalScreen wrapping this screen.

Available where Container: UINavigationController

  • eraseToAnyStackScreen() Extension method

    Wraps this screen with a type eraser.

    Use eraseToAnyStackScreen() to expose an instance of AnyStackScreen, rather than this screen’s actual type. This form of type erasure preserves abstraction across API boundaries, such as different modules. When you expose your screens as the AnyStackScreen type, you can change the underlying implementation over time without affecting existing clients.

    See also

    AnyStackScreen

    See also

    AnyScreen

    Declaration

    Swift

    public func eraseToAnyStackScreen() -> AnyStackScreen

    Return Value

    An AnyStackScreen wrapping this screen.

Available where Container: UITabBarController

  • eraseToAnyTabsScreen() Extension method

    Wraps this screen with a type eraser.

    Use eraseToAnyTabsScreen() to expose an instance of AnyTabsScreen, rather than this screen’s actual type. This form of type erasure preserves abstraction across API boundaries, such as different modules. When you expose your screens as the AnyTabsScreen type, you can change the underlying implementation over time without affecting existing clients.

    See also

    AnyTabsScreen

    See also

    AnyScreen

    Declaration

    Swift

    public func eraseToAnyTabsScreen() -> AnyTabsScreen

    Return Value

    An AnyTabsScreen wrapping this screen.

Available where Container: UIViewController

Available where Container: UINavigationController

Available where Container: UITabBarController