协调器模式 - 使用Storyboard而不是Xib

时间:2018-05-17 10:55:45

标签: ios swift xib xcode-storyboard coordinator-pattern

这是我第一次参加协调员模式。虽然我已经意识到它的重要性,但我有一个主要的问题 我在这个模式上经历了this惊人的文章。事实上,我可以使用它自己构建一个演示项目。但有一点 - 建议使用 Xib 。并不是唯一提到故事板不能使用,但是在文章末尾经过这些线条,这让我想到了:

  

强大的力量带来了巨大的责任(和限制)。使用   在此扩展中,您需要为每个扩展创建一个单独的故事板   UIViewController中。故事板的名称必须与名称相匹配   UIViewController的类。必须将此UIViewController设置为   这个故事板的初始UIViewController。

有人提到,在故事板的情况下,我们应该创建一个扩展并在UIViewController中使用它:

extension MyViewController: StoryboardInstantiable {
}  

StoryboardInstantiable:

import UIKit

protocol StoryboardInstantiable: NSObjectProtocol {
  associatedtype MyType  // 1
  static var defaultFileName: String { get }  // 2
  static func instantiateViewController(_ bundle: Bundle?) -> MyType // 3
}

extension StoryboardInstantiable where Self: UIViewController {
  static var defaultFileName: String {
    return NSStringFromClass(Self.self).components(separatedBy: ".").last!
  }

  static func instantiateViewController(_ bundle: Bundle? = nil) -> Self {
    let fileName = defaultFileName
    let sb = UIStoryboard(name: fileName, bundle: bundle)
    return sb.instantiateInitialViewController() as! Self
  }
}

查询:

  1. 由于作者提到必须为每个UIViewController创建单独的 Storyboard ,如何在协调器模式中使用Xib更好的方法?
  2. 为什么我们需要为每个UIViewController创建单独的 Storyboard ?我们不能通过使用segues链接任何UIViewController来使用UIViewController的故事板标识符吗?这种方式可以使用标识符调整上述扩展名并轻松实现相同的目的。

5 个答案:

答案 0 :(得分:8)

我已多次阅读该教程,并且每个View控制器使用一个协调器,对我来说没有意义。我认为协调员的目的是将导航逻辑从视图控制器转移到可以管理整体流程的更高级别对象。

如果您想从主故事板初始化ViewControllers,请改用此协议和扩展程序:

import UIKit

protocol Storyboarded {
    static func instantiate() -> Self
}

extension Storyboarded where Self: UIViewController {
    static func instantiate() -> Self {
        // this pulls out "MyApp.MyViewController"
        let fullName = NSStringFromClass(self)

        // this splits by the dot and uses everything after, giving "MyViewController"
        let className = fullName.components(separatedBy: ".")[1]

        // load our storyboard
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

        // instantiate a view controller with that identifier, and force cast as the type that was requested
        return storyboard.instantiateViewController(withIdentifier: className) as! Self
    }
}

唯一的要求是它使用的每个View控制器都具有此协议,并且具有与该类同名的StoryboardID。

你可以这样使用它:

private func startBlueFlow() {
    let vc = BlueViewControllerOne.instantiate()
    vc.coordinator = self
    self.navigationController.push(vc, animated: true)
}

免责声明:this article获取的协议可能对您有所帮助

更新:(已添加参考)

Soroush Khanlou在iOS和Redux中关于协调器模式的其他文章和教程中通常被记入和引用。他有一篇文章here(日期为2015年,代码为objective-c),您可能会发现这篇文章很有趣。

答案 1 :(得分:2)

我使用协调员与故事板的方式是使用多个故事板。每个功能/模块一个故事板。

为什么要使用多个故事板而不是一个?在处理大量功能和团队时,最好分割你的故事板,因为只使用一个故事板会导致很多合并冲突并修复故事板git冲突是痛苦之一iOS开发人员。

我是这样做的。

首先,我有一个名为AppStoryboardType的协议,我将在枚举上实现,其中包含我所有故事板的名称。

protocol AppStoryboardType {
    var instance: UIStoryboard { get }

    func instantiate<T: UIViewController>(_ viewController: T.Type, function: String, line: Int, file: String) -> T

    func instantiateInitialViewController() -> UIViewController?
}

extension AppStoryboardType {
    func instantiateInitialViewController() -> UIViewController? {
        return self.instance.instantiateInitialViewController()
    }
}

extension AppStoryboardType where Self: RawRepresentable, Self.RawValue == String {
    var instance: UIStoryboard {
        return UIStoryboard(name: self.rawValue, bundle: nil)
    }

    func instantiate<T: UIViewController>(
        _ viewController: T.Type,
        function: String = #function,
        line: Int = #line,
        file: String = #file) -> T {

        let storyboardID: String = T.storyboardIdentifier

        guard let vc = self.instance.instantiateViewController(withIdentifier: storyboardID) as? T else {
            fatalError("ViewController with identifier \(storyboardID), not found in \(self.rawValue) Storyboard.\nFile : \(file) \nLine Number : \(line) \nFunction : \(function)")
        }

        return vc
    }
}

enum AppStoryboard: String, AppStoryboardType {
    case Main /* ... Insert your other storyboards here. */

    // These are the refactored modules that use coordinator pattern.
    case PasswordRecovery, Registration
}

extension UIViewController {
    public static var defaultNibName: String {
        return self.description().components(separatedBy: ".").dropFirst().joined(separator: ".")
    }

    static var storyboardIdentifier: String {
        return "\(self)"
    }

    static func instantiate(fromAppStoryboard appStoryboard: AppStoryboard) -> Self {
        return appStoryboard.instantiate(self)
    }
}

现在我已经向您展示了我使用的基础,以下是它如何在代码上实现。

let viewController = AppStoryboard.Login.instantiate(LoginViewController.self)
viewController./// set properties if ever you need to set them
presenter.present(viewController, animated: true, completion: nil)

PS:每个模块/功能的大部分时间我都有自己的故事板协调员,但这取决于UIViewController的可重用性你会用。

修改

1年后,我现在停止使用AppStoryboard方法,现在使用Reusable库。这背后的原因是它更清洁,更不容易出现人为错误。

现在代替程序员(我们)需要知道特定VC附加到哪个故事板,我们现在可以简单地将viewcontroller子类化为StoryboardSceneBased,提供该viewcontroller的故事板并简单地通过它实例化它做CustomViewController.instantiate()

// From this code
let viewController = AppStoryboard.Login.instantiate(LoginViewController.self)

// To this code
let viewController = LoginViewController.instantiate()

答案 2 :(得分:0)

重要的是要了解协调器控制应用程序的流程,并且可以根据需要编写逻辑。但是,如果您编写其余的逻辑,并遵循最佳实践,您将理解为什么这样做的原因以及它的作用。

协调器与体系结构其他组件交互的整个逻辑可以通过以下方案描述。

Application coordinator scheme

  

授权屏幕的实现示例(路由器,协调器工厂,流程工厂,基础协调器,子协调器)

Architecture implementation example (SOA/Application Coordinator/VIPER)

看这个项目。我相信你会喜欢的。 希望我能帮助您了解协调员这个美好的话题。

答案 3 :(得分:0)

我使用了enum并更改了instanciate()方法。一切对我来说都很好

enum OurStoryboards: String{
    case MainPage = "MainPage"
    case Catalog = "Catalog"
    case Search = "Search"
    case Info = "Info"
    case Cart = "Cart"
}

protocol Storyboarded {
    static func instantiate(_ storyboardId: OurStoryboards) -> Self
}

extension Storyboarded where Self: UIViewController {
    static func instantiate(_ storyboardId: OurStoryboards) -> Self {

        let id = String(describing: self)
        // load our storyboard
        var storyboard = UIStoryboard()
        switch storyboardId {
        case .MainPage:
            storyboard = UIStoryboard(name: OurStoryboards.MainPage.rawValue ,bundle: Bundle.main)
        case .Catalog:
            storyboard = UIStoryboard(name: OurStoryboards.Catalog.rawValue ,bundle: Bundle.main)
        case .Search:
            storyboard = UIStoryboard(name: OurStoryboards.Search.rawValue ,bundle: Bundle.main)
        case .Info:
            storyboard = UIStoryboard(name: OurStoryboards.Info.rawValue ,bundle: Bundle.main)
        case .Cart:
            storyboard = UIStoryboard(name: OurStoryboards.Cart.rawValue ,bundle: Bundle.main)
        }
        // instantiate a view controller with that identifier, and force cast as the type that was requested
        return storyboard.instantiateViewController(withIdentifier: id) as! Self
    }

}

答案 4 :(得分:0)

您实际上问了两个问题,所以我也将答案分为两个部分:

关于XIB与情节提要

我看到一个很少的理由,为什么在使用协调器模式时,为什么要使用xib而不是情节提要作为视图控制器。我想到的xib的一个优点是,当使用xib时,您可以在以后为给定的视图控制器使用不同的子类,并将其与相同的xib一起使用。例如,假设您为EmployeesViewController类创建了一个xib,则稍后可以创建具有修改功能的AdministratorsViewControllers子类,并使用与之前创建的相同的xib对其进行初始化。使用情节提要,您无法执行此操作,因为视图控制器的类已在情节提要上设置,并且无法更改。例如,如果您正在创建框架,并且想让用户在保留UI的同时继承基类,那么类似的事情可能会很有用。但是,在大多数情况下,您可能不需要执行此类操作。另一方面,使用情节提要板将使您能够访问功能,例如情节提要板上的表格视图单元原型,表格视图控制器中的静态单元格以及使用xib时不可用的其他功能。因此,尽管在某些情况下xib更好,但在大多数情况下,情节提要可能会更有用。

关于为每个UIViewController创建单独的故事板

正如您所注意到的,您可以使用情节提要标识符,而不必将每个视图控制器拆分为单独的情节提要(也如此处其他答案所示)。将每个视图控制器放在单独的情节提要板中看起来并不很典型,但是实际上并没有最初看起来那样毫无意义。可能的最大优点是,当您将每个视图控制器放在单独的情节提要中时,在团队中工作时,通常会减少git上的合并冲突(特别是因为有时xcode会更改情节提要中其他视图控制器中某些属性的某些值,即使您请勿修改它们)。这也使您的团队更快速,更轻松地审查代码。此外,如果这些情节提要具有一些通用的UI,则将它们复制到另一个项目中也更加容易。例如,如果您在一家为各种客户创建特定类型的应用程序的公司中工作,这可能会很有用。因此,如您所见,这里有一些优势,但选择权取决于您。我不会说这种方法是好是坏。我认为两者都很好,这更多是一个偏好问题。只需选择一个您更喜欢并且更适合您的产品即可。