3

这是我第一次使用协调器模式。虽然我已经意识到它的重要性,但我有一个主要问题。
我浏览了这篇关于这种模式的精彩文章。事实上,我能够使用它自己构建一个演示项目。不过有一点 - 建议使用Xib。并不是专门提到不能使用故事板,但是在文章结尾处通过这些行,让我不这么认为:

强大的力量伴随着巨大的责任(和限制)。要使用这个扩展,你需要为每个 UIViewController 创建一个单独的故事板。故事板的名称必须与 UIViewController 类的名称匹配。此 UIViewController 必须设置为此故事板的初始 UIViewController。

它提到,在Storyboards的情况下,我们应该创建一个扩展并将其用于UIViewController

extension MyViewController: 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. 正如作者提到的,必须为每个创建单独的StoryboardUIViewController,如何在Coordinator 模式中使用 Xib 是一种更好的方式?
  2. 为什么我们需要为每个创建单独的故事板UIViewController?我们UIViewController不能通过不链接任何UIViewController使用 segues 来使用 's storyboard 标识符吗?这样可以使用标识符调整上述扩展名并轻松实现。
4

4 回答 4

8

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

如果您想从主故事板初始化 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)
}

免责声明:本文中的协议也可能对您有所帮助

更新:(添加参考)

Soroush Khanlou在其他关于 iOS 和 Redux 中的协调器模式的文章和教程中得到了普遍认可和引用。他在这里有一篇文章(日期为 2015 年,objective-c 中的代码),您可能会发现该文章很有趣。

于 2018-05-17T11:00:52.340 回答
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:大多数时候,每个模块/功能我都有自己的StoryboardCoordinator,但这取决于UIViewController您将使用的可重用性。

编辑

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

现在,程序员(我们)不需要知道特定的 VC 附加到哪个故事板,我们现在可以简单地将视图控制器子类StoryboardSceneBased化为,提供该视图控制器的故事板并简单地实例化它CustomViewController.instantiate()

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

// To this code
let viewController = LoginViewController.instantiate()
于 2018-05-26T03:07:57.010 回答
1

我使用了枚举并更改了 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
    }

}
于 2019-02-20T09:30:36.893 回答
1

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

关于 XIB 与故事板

在使用协调器模式时,我几乎没有看到为什么要使用 xib 而不是故事板作为视图控制器的原因。我想到的 xib 的一个优点是,当使用 xib 时,您可以稍后为给定的视图控制器使用不同的子类并使用相同的 xib。例如,假设您为EmployeesViewController 类创建了一个xib,您可以稍后创建具有修改功能的AdministratorsViewControllers 子类,并使用您之前创建的相同xib 对其进行初始化。使用情节提要您不能这样做,因为视图控制器的类已经在情节提要上设置并且无法更改。例如,如果您正在创建一个框架并且您希望让用户能够在保留您的 UI 的同时对您的基类进行子类化,那么类似的东西可能会很有用。但是在大多数情况下,您可能不需要做任何类似的事情。另一方面,使用情节提要可以让您访问诸如情节提要上的表格视图单元格原型、表格视图控制器中的静态单元格以及使用 xibs 时不可用的其他功能。因此,虽然在某些情况下 xib 更好,但在大多数情况下,故事板可能会更有用。

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

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

于 2019-12-27T10:53:00.117 回答