Skip to content

Commit

Permalink
[Refactor] Coordinator 기본 코드 세팅 DO-SOPT-iOS-Part#20
Browse files Browse the repository at this point in the history
  • Loading branch information
HELLOHIDI committed Jul 5, 2024
1 parent eaa13a3 commit 66c2503
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 13 deletions.
13 changes: 0 additions & 13 deletions Projects/Features/BaseFeatureDependency/Sources/Coordinator.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Coordinator.swift
// ProjectDescriptionHelpers
//
// Created by 류희재 on 2023/10/30.
//

import Foundation

/// 모든 Coordinator가 따르는 Coordinator 프로토콜입니다.
public protocol Coordinator: AnyObject {
/// start 메서드는 Coordinator가 담당하는 플로우를 시작하게 합니다. 대개의 경우 화면전환이 이뤄집니다.
func start()

/// DeepLink와 함께 화면전환을 하기 위한 star 메서드입니다.
/// - Parameter option: DeepLinkOption을 추가하여 DeepLink 기능을 사용할 수 있습니다.
// func start(with option: DeepLinkOption?)

/// CoordinatorStaringOption을 지정하여 원하는 화면전환 액션을 수행합니다.
/// - Parameter style: modal 또는 Root 등의 옵션을 지정합니다.
func start(by style: CoordinatorStartingOption)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// BaseCoordinator.swift
// BaseFeatureDependency
//
// Created by 류희재 on 7/5/24.
// Copyright © 2024 Weather-iOS. All rights reserved.
//

import Foundation

open class BaseCoordinator: Coordinator {

// MARK: - Vars & Lets

public var childCoordinators = [Coordinator]()

// MARK: - Public methods

/// 자식 코디네이터의 의존성을 추가하여 메모리에서 해제되지 않도록 합니다.
public func addDependency(_ coordinator: Coordinator) {
for element in childCoordinators {
if element === coordinator { return }
}
childCoordinators.append(coordinator)
}

/// 자식 코디네이터의 의존성을 제거하여 메모리에서 해제되도록 합니다.
public func removeDependency(_ coordinator: Coordinator?) {
guard childCoordinators.isEmpty == false, let coordinator = coordinator else { return }

for (index, element) in childCoordinators.enumerated() {
if element === coordinator {
childCoordinators.remove(at: index)
break
}
}
}

// MARK: - Coordinator

open func start() {
// start(with: nil)
}

// open func start(with option: DeepLinkOption?) {
//
// }

open func start(by style: CoordinatorStartingOption) {

}

public init(childCoordinators: [Coordinator] = [Coordinator]()) {
self.childCoordinators = childCoordinators
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// CoordinatorFinishOutput.swift
// BaseFeatureDependency
//
// Created by 류희재 on 7/5/24.
// Copyright © 2024 Weather-iOS. All rights reserved.
//

import Foundation

/// Coordinator가 자신의 플로우를 마쳤을 때 호출됩니다. 대개의 경우 removeDependency()가 호출됩니다.
public protocol CoordinatorFinishOutput {
var finishFlow: (() -> Void)? { get set }
}

public typealias DefaultCoordinator = BaseCoordinator & CoordinatorFinishOutput
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// CoordinatorStartingOption.swift
// BaseFeatureDependency
//
// Created by 류희재 on 7/5/24.
// Copyright © 2024 Weather-iOS. All rights reserved.
//

import Foundation

public enum CoordinatorStartingOption {
case root
case modal
case push
case rootWindow(animated: Bool, message: String?)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
//
// Router.swift
// BaseFeatureDependency
//
// Created by 류희재 on 7/5/24.
// Copyright © 2024 Weather-iOS. All rights reserved.
//

import UIKit

import Core
import SafariServices

/// RouterProtocol은 Coordinator에서 화면전환의 종류를 지정합니다.
public protocol RouterProtocol: ViewControllable {

var topViewController: UIViewController? { get }

func present(_ module: ViewControllable?)
func present(_ module: ViewControllable?, animated: Bool)
func present(_ module: ViewControllable?, animated: Bool, modalPresentationSytle: UIModalPresentationStyle)
func present(_ module: ViewControllable?, animated: Bool, completion: (() -> Void)?)

func push(_ module: ViewControllable?)
func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?)
func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?, animated: Bool)
func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?, animated: Bool, completion: (() -> Void)?)

func popModule()
func popModule(transition: UIViewControllerAnimatedTransitioning?)
func popModule(transition: UIViewControllerAnimatedTransitioning?, animated: Bool)

func dismissModule(animated: Bool)
func dismissModule(animated: Bool, completion: (() -> Void)?)

func setRootModule(_ module: ViewControllable?, animated: Bool)
func setRootModule(_ module: ViewControllable?, hideBar: Bool, animated: Bool)

func popToRootModule(animated: Bool)
func popToModule(module: ViewControllable?, animated: Bool)

func showTitles()
func hideTitles()
}

/// RouterProtocol을 채택하여 Coordinator가 모르는 화면전환의 기능을 수행합니다. RootController를 가지고 다양한 기능을 수행합니다.
public
final class Router: NSObject, RouterProtocol {



// MARK: - Vars & Lets

private weak var rootController: UINavigationController?
private var completions: [UIViewController : () -> Void]
private var transition: UIViewControllerAnimatedTransitioning?

public var topViewController: UIViewController? {
self.rootController?.topViewController
}

// MARK: - ViewControllable

public var viewController: UIViewController {
return self.rootController ?? UIViewController()
}
public var asNavigationController: UINavigationController {
return rootController ?? UINavigationController(rootViewController: viewController)
}

// MARK: - RouterProtocol

public func present(_ module: ViewControllable?) {
self.present(module, animated: true)
}

public func present(_ module: ViewControllable?, animated: Bool) {
guard let controller = module?.viewController else { return }
self.rootController?.present(controller, animated: animated, completion: nil)
}

public func present(_ module: ViewControllable?, animated: Bool, modalPresentationSytle: UIModalPresentationStyle) {
guard let controller = module?.viewController else { return }
controller.modalPresentationStyle = modalPresentationSytle
self.rootController?.present(controller, animated: animated, completion: nil)
}

public func present(_ module: ViewControllable?, animated: Bool, modalPresentationSytle: UIModalPresentationStyle, modalTransitionStyle: UIModalTransitionStyle) {
guard let controller = module?.viewController else { return }
controller.modalPresentationStyle = modalPresentationSytle
controller.modalTransitionStyle = modalTransitionStyle
self.rootController?.present(controller, animated: animated, completion: nil)
}

public func present(_ module: ViewControllable?, animated: Bool, completion: (() -> Void)?) {
guard let controller = module?.viewController else { return }
self.rootController?.present(controller, animated: animated, completion: completion)
}

public func push(_ module: ViewControllable?) {
self.push(module, transition: nil)
}

public func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?) {
self.push(module, transition: transition, animated: true)
}

public func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?, animated: Bool) {
self.push(module, transition: transition, animated: animated, completion: nil)
}

public func push(_ module: ViewControllable?, transition: UIViewControllerAnimatedTransitioning?, animated: Bool, completion: (() -> Void)?) {
self.transition = transition

guard let controller = module?.viewController,
(controller is UINavigationController == false)
else { assertionFailure("Deprecated push UINavigationController."); return }

if let completion = completion {
self.completions[controller] = completion
}
self.rootController?.pushViewController(controller, animated: animated)

self.transition = nil
}

public func popModule() {
self.popModule(transition: nil)
}

public func popModule(transition: UIViewControllerAnimatedTransitioning?) {
self.popModule(transition: transition, animated: true)
}

public func popModule(transition: UIViewControllerAnimatedTransitioning?, animated: Bool) {
self.transition = transition

if let controller = rootController?.popViewController(animated: animated) {
self.runCompletion(for: controller)
}

self.transition = nil
}

public func popToModule(module: ViewControllable?, animated: Bool = true) {
if let controllers = self.rootController?.viewControllers , let module = module {
for controller in controllers {
if controller == module as! UIViewController {
self.rootController?.popToViewController(controller, animated: animated)
self.runCompletion(for: controller)
break
}
}
}
}

public func dismissModule(animated: Bool) {
self.dismissModule(animated: animated, completion: nil)
}

public func dismissModule(animated: Bool, completion: (() -> Void)?) {
self.rootController?.dismiss(animated: animated, completion: completion)
}

public func setRootModule(_ module: ViewControllable?, animated: Bool) {
self.setRootModule(module, hideBar: false, animated: animated)
}

public func setRootModule(_ module: ViewControllable?, hideBar: Bool, animated: Bool) {
guard let controller = module?.viewController else { return }
self.rootController?.setViewControllers([controller], animated: animated)
self.rootController?.isNavigationBarHidden = hideBar
}

public func popToRootModule(animated: Bool) {
if let controllers = self.rootController?.popToRootViewController(animated: animated) {
controllers.forEach { controller in
self.runCompletion(for: controller)
}
}
}

public func showTitles() {
self.rootController?.isNavigationBarHidden = false
self.rootController?.navigationBar.prefersLargeTitles = true
self.rootController?.navigationBar.tintColor = UIColor.black
}

public func hideTitles() {
self.rootController?.isNavigationBarHidden = true
}

// MARK: - Private methods

private func runCompletion(for controller: UIViewController) {
guard let completion = self.completions[controller] else { return }
completion()
completions.removeValue(forKey: controller)
}

// MARK: - Init methods

public init(rootController: UINavigationController) {
self.rootController = rootController
self.completions = [:]
super.init()
}
}

extension Router: UINavigationControllerDelegate {
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self.transition
}
}
Binary file modified graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 66c2503

Please sign in to comment.