Skip to content

Instantly share code, notes, and snippets.

@audrl1010
Created April 6, 2019 15:09
Show Gist options
  • Save audrl1010/6665019d20a5d1dcf0dc6853a1b15752 to your computer and use it in GitHub Desktop.
Save audrl1010/6665019d20a5d1dcf0dc6853a1b15752 to your computer and use it in GitHub Desktop.
RxCoordinator 분석
// completion handler for transitions
typealias PresentationHandler = () -> Void

// completion handler for transitions, which also provides the context
// information about the transition.
typealias ContextPresentationHandler = (
	PresentationHandlerContext
) -> Void

// 화면 전환에 필요를 느낄 때, 신호를 보내는 데 사용되는 친구네..
protocol Router: Presentable {
	associatedtype RouteType: Route
	func contextTrigger(_ route: RouteType, with options: TransitionOptions, completion: ContextPresentationHandler?) 
}

extension Router {
	// 이걸로 화면 전환에 필요하다는 것을 알릴 수 있음
	func trigger(_ route: RouteType, with options: TransitionOptions, completion: PresentationHandler?) {
		contextTrigger(route, with: options) { _ in completion?() }
	}
}

extension Router where Self: Presentable {
	var anyRouter: AnyRouter<RouteType> {
		return AnyRouter(self)
	}
}

// 특정 Route의 구현을 제공
final class AnyRouter<RouteType: Route>: Router {
	private let _contextTrigger: (
		RouteType,
	  TransitionOptions,
		ContextPresentationHandler?
	) -> Void
	private let _trigger: (
		RouteType,
		TransitionOptions,
		PresentationHandler?
	) -> Void
	private let _presented: (Presentable?) -> Void
	private let _viewController: () -> UIViewController?
	private let _setRoot: (UIWindow) -> Void
	
	init(T: Router>(_ router: T) where T.RouteType == RouteType {
		_trigger = router.trigger
    _presented = router.presented
    _viewController = { router.viewController }
    _setRoot = router.setRoot
    _contextTrigger = router.contextTrigger
	}
}

// routes를 trigger할 수 있어야 하고 transition을 perform할 수 있어야 한다.
protocol Coordinator: Router, TransitionPerformer {
	func prepareTransition(for route: RouteType) -> TransitionType
}

// Typealiases
extension Coordinator {
	typealias RootViewController = TransitionType.RootViewController
}

// Presentable
extension Coordinator {
	var viewController: UIViewController! {
		return rootViewController
	}
}

// Default implementations
extension Coordinator {
	var anyCoordinator: AnyCoordinator<RouteType, TransitionType> {
		return AnyCoordinator(self)
	}
	
	func presented(from presentable: Presentable?) {}
	
	func contextTrigger(
		_ route: RouteType,
		with options: TransitionOptions,
		completion: ContextPresentationHandler?
	) {
		// 이게 실제 route 처리하는 것을 의미.
		let transition = prepareTransition(for: route)
		let context = PresentationHandlerContext(presentables: transition.presentables)
		performTransition(transition, with: options) { completion?(context) }
	}

	func performTransition(
		_ transition: TransitionType,
		with options: TransitionOptions,
		completion: PresentationHandler? = nil
	) {
		transition.perform(on: rootViewController, with: options, completion: completion)
	}
}


protocol Route {}
protocol TransitionProtocol {
	associatedtype RootViewController: UIViewController
	var presentables: [Presentable] { get }
	var animation: TransitionAnimation? { get }
	func perform(
		on rootViewController: RootViewController,
		with options: TransitionOptions,
		completion: PresentationHandler?
	)
	static func multiple(_ transitions: [Self]) -> Self
}

// 주어진 route 발생 시 -> transition 실행
class BaseCoordinator<RouteType: Route, transitionType: TransitionProtocol>: Coordinator {
	private let rootViewControllerBox = ReferenceBox<RootViewController>()
	private var gestureRecognizerTargets = [GestureRecognizerTarget]()
	// ?
	var rootViewController: RootViewController {
		return rootViewControllerBox.get()!
	}

	init(initialRoute: RouteType?) {
		rootViewControllerBox.set(generateRootViewController())
		initialRoute
			.map(prepareTransition)
			.map(performTransitionAfterWindowAppeared)
	}

	func presented(from presentable: Presentable?) {
		// 보여지기 전까지는 참고 있다는 건가...
		rootViewControllerBox.releaseStrongReference()
	}

	func generateRootViewController() -> RootViewController {
		return RootViewController()
	}

	func prepareTransition(for route: RouteType) -> TransitionType {
		....
	}

	private func performTransitionAfterWindowAppeared(_ transition: TransitionType) {
	}
}

class NavigationAnimationDelegate: NSObject {
	private static let interactivePopGestureRecognizerDelegateAction = Selector(("handleNavigationTransition:"))
	var velocityThreshold: CGFloat { return UIScreen.main.bounds.width / 2 }
	var transitionProgressThreshold: CGFloat { return 0.5 }
	private var animations = [Animation?]()
	private var interactivePopGestureRecognizerDelegate: UIGestureRecognizerDelegate?
	internal weak var delegate: UINavigationControllerDelegate?
  private weak var navigationController: UINavigationController?
}

// User에 보여질 수 있는 것은 무엇이든 가능, Coordinators, View Controllers
// Transition할 때 Presentable을 사용... (S -> E)
protocol Presentable {
	// viewController는 self 리턴.
	// coordinator는 rootViewController 리턴.
	var viewController: UIViewController! { get }

	// 특정 route에 작동할 수 있는 presentable을 찾는 데 사용하며
	// Deep linking은 이 메소드를 사용해 특정 route를 작동시킨다.
	func router<R: Route>(for route: R) -> AnyRouter<R>?
	
	// Presentable이 user에 보여졌을 때 호출되어진다.
	func presented(from presentable: Presentable?)
	
	// Presentable을 window의 root로 설정하며 window key and visible을 만든다.
	func setRoot(for window: UIWindow)
}

/// TransitionProtocol의 공통 구현을 나타낸다.
struct Transition<RootViewController: UIViewController>: TransitionProtocol {
	// transition에 사용할 type.
	typealias PerformClosure = (
		_ rootViewController: RootViewController,
		_ options: TransitionOptions,
		_ completion: PresentationHandler?
	) -> Void
	
	private var _presentables: [Presentable]
	private var _animation: TransitionAnimation?
	private var _perform: PerformClosure
	
	// deep-linking에서 사용에 유용하다.
	var presentables: [Presentable] {
		return _presentables
	}
	// presentation or dismissal animation에 사용되어진다.
	var animation: TransitionAnimation? {
		return _animation
	}
}

typealias NavigationTransition = Transition<UINavigationController>

extension Transition where RootViewController: UINavigationController {
	static func push(_ presentable: Presentable, animation: Animation? = nil) -> NavigationTransition {
		return NavigationTransition(
			presentables: [presentable],
			animationInUse: animation?.presentationAnimation,
		) {
			rootViewController.push(presentable.viewController, with: options, animation: animation) {
				presentable.presented(from: rootViewController)
				completion?()
			}
		}
	}
	static func pop(animation: Animation? = nil) -> NavigationTransition {
		return NavigationTransition(
			presentables: [],
			animationInUse: animation?.dismissalAnimation
		) { rootViewController, options, completion in
        rootViewController.pop(
					toRoot: false,
					with: options,
					animation: animation,
					completion: completion
				)
    }
	}
	static func popToRoot(animation: Animation? = nil) -> NavigationTransition
	set(_ presentables: [Presentable], animation: Animation? = nil) -> NavigationTransition
}

class NavigationCoordinator<RouteType: Route>
: BaseCoordinator<RouteType, NavigationTransition> {
	private let animationDelegate = NavigationAnimationDelegate()
	var delegate: UINavigationControllerDelegate? {
    get { return animationDelegate.delegate }
    set { animationDelegate.delegate = newValue }
  }
	override func generateRootViewController() -> UINavigationController {
		let navigationController = super.generateRootViewController()
		navigationController.delegate = animationDelegate
		return navigationController
  }
}



Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment