繁体   English   中英

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

[英]Coordinator pattern - Using Storyboard instead of Xib

这是我第一次使用coordinator 模式 虽然我已经意识到它的重要性,但我有一个主要的担忧。
我经历了这个在这个模式惊人的文章。 事实上,我能够使用它自己构建一个演示项目。 不过有一点 - 建议使用Xib 并不是专门提到不能使用Storyboards ,但是在文章结尾处通过这些行,让我不这么认为:

强大的力量伴随着巨大的责任(和限制)。 要使用这个扩展,你需要为每个 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. 正如作者提到的,必须为每个UIViewController创建单独的Storyboard ,如何在Coordinator 模式中更好地使用 Xib ?
  2. 为什么我们需要为每个UIViewController创建一个单独的Storyboard 我们不能通过不使用 segues 链接任何UIViewController来使用UIViewController的故事板标识符吗? 这样可以使用标识符调整上述扩展并轻松实现相同的功能。

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

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

唯一的要求是它使用的每个视图控制器都有这个协议,并且有一个与类同名的 StoryboardID。

你可以这样使用它:

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

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

更新:(添加参考)

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

我在故事板中使用协调器的方法是使用多个故事板。 每个功能/模块一个故事板。

为什么是多个故事板而不是一个? 当与许多功能和团队一起工作时,最好拆分你的故事板,因为只使用一个故事板会导致很多合并冲突,而修复故事板 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的可重用性。

编辑

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

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

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

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

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

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
    }

}

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

关于 XIB 与故事板

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

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

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

重要的是要理解协调器控制应用程序的流程,逻辑可以根据需要编写。 但是,如果您编写其余逻辑,请遵循最佳实践,您将理解为什么以这种方式完成它以及它给您的内容。

协调器和架构的其他组件的交互的整个逻辑可以通过以下方案来描述。

应用协调员计划

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

架构实现示例(SOA /应用程序协调器/ VIPER)

看看这个项目。 我相信你会喜欢它。 我希望我能帮助你理解这个协调员的精彩话题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM