Skip to content

DevAndArtist/RxContainer

Repository files navigation


This Swift module introduces a minimal custom ContainerViewController, which does not contain any unnecessary APIs like UINavigationViewController, nor rely on any protocols such as UIViewControllerTransitioningDelegate etc. for custom transitions.

The end-user is resposible to create custom animators, by conforming to the minimal Animator protocol, which will drive the transitions on the container-view-controller. The ContainerViewController has three different options for pop or push transitions: animated, interactive and immediate.

ContainerViewController:
open class ContainerViewController : UIViewController {
    ///
    open protocol Delegate : AnyObject {
        ///
        func animator(for transition: Transition) -> Animator?
    }

    ///
    public struct Event {
        ///
        public enum Position { case start, end }

        ///
        public let operation: Operation

        ///
        public var position: Position { get }

        ///
        public let containerViewController: ContainerViewController
    }

    ///
    public struct Operation {
        ///
        public enum Kind {
            case push(UIViewController)
            case pop(UIViewController)
            case set([UIViewController])
        }
        
        ///
        public let kind: Kind

        ///
        public let isAnimated: Bool
    }
    
    ///
    public enum Option {
        case animated, interactive, immediate
    }
    
    ///
    open var containerView: UIView { get }

    ///
    open var viewControllers: [UIViewController]

    ///
    open var rootViewController: UIViewController? { get }

    ///
    open var topViewController: UIViewController? { get }

    ///
    open weak var delegate: Delegate?

    /// Initializes and returns a newly created container view controller.
    public init()

    /// Initializes and returns a newly created container view controller.
    ///
    /// This is a convenience method for initializing the receiver and
    /// pushing view controllers onto the view controller stack. Every
    /// view controller stack must have at least one view controller to 
    /// act as the root.
    public convenience init(_ viewControllers: UIViewController...)

    /// Initializes and returns a newly created container view controller.
    ///
    /// This is a convenience method for initializing the receiver and
    /// pushing view controllers onto the view controller stack. Every
    /// view controller stack must have at least one view controller to 
    /// act as the root.
    public convenience init(_ viewControllers: [UIViewController])

    ///
    required public init?(coder aDecoder: NSCoder)

    ///
    open func push(
        _ viewController: UIViewController, 
        option: Option = .animated, 
        with animator: (Transition) -> Animator = RxContainer.animator(for:)
    )
    
    ///
    @discardableResult
    open func pop(
        option: Option = .animated, 
        with animator: (Transition) -> Animator = RxContainer.animator(for:)
    ) -> UIViewController?
    
    ///
    @discardableResult
    open func pop(
        to viewController: UIViewController, 
        option: Option = .animated,
        with animator: (Transition) -> Animator = RxContainer.animator(for:)
    ) -> [UIViewController]?
    
    ///
    @discardableResult
    open func popToRootViewController(
        option: Option = .animated,
        with animator: (Transition) -> Animator = RxContainer.animator(for:)
    ) -> [UIViewController]?
    
    ///
    open func setViewControllers(
        _ viewControllers: [UIViewController], 
        option: Option = .animated,
        with animator: (Transition) -> Animator = RxContainer.animator(for:)
    )
}

Reactive extension:

extension Reactive where Base : ContainerViewController {
	///
	public var event: Signal<ContainerViewController.Event>
}
Transition:
public final class Transition {
    ///
    public enum CompletionPosition { case start, end }

    ///
    public struct Context {

        ///
        public enum Key { case from, to }

        ///
        public enum Kind { case push, pop }

        ///
        public let kind: Kind

        ///
        public let containerView: UIView

        ///
        public let isAnimated: Bool

        ///
        public let isInteractive: Bool

        ///
        public func viewController(forKey key: Key) -> UIViewController

        ///
        public func view(forKey key: Key) -> UIView
    }

    ///
    public var additionalAnimation: ((Context) -> Void)? { get }
    
    ///
    public var additionalCompletion: ((Context) -> Void)? { get }
    
    ///
    public let context: Context
    
    ///
    public func animateAlongside(_ animation: ((Context) -> Void)?)
    
    ///
    public func animateAlongside(
        _ animation: ((Context) -> Void)?, 
        completion: ((Context) -> Void)? = default
    )

    ///
    public func complete(at position: CompletionPosition)
}
Animator:
open protocol Animator : AnyObject {
    ///
    var transition: Transition { get }

    ///
    func animate()
    
    // Default implementation (no-op)
    func transition(completed: Bool)
}
DefaultAnimator:
public final class DefaultAnimator : Animator {
    ///
    public enum Direction { case left, right, up, down }

    ///
    public enum Style { case overlap, slide }    
    
    ///
    public enum Order { case normal, reversed }

    ///
    public let transition: Transition

    ///
    public let direction: Direction
    
    ///
    public let style: Style

    ///
    public let order: Order

    ///
    public init(
        for transition: Transition,
        withDirection direction: Direction,
        style: Style = .overlap,
        order: Order = .normal
    )

    ///
    public func animate()
}

/// Default function for any transition.
public func animator(for transition: Transition) -> Animator