【问题标题】:Coordinator pattern - Using Storyboard instead of Xib协调器模式 - 使用 Storyboard 代替 Xib
【发布时间】:2018-10-27 14:52:55
【问题描述】:

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

强大的力量伴随着巨大的责任(和限制)。使用 这个扩展,你需要为每个单独的故事板 界面视图控制器。故事板的名称必须与 UIViewController 的类。此 UIViewController 必须设置为 此情节提要的初始 UIViewController。

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

【问题讨论】:

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


    【解决方案1】:

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

    如果您想从主情节提要初始化 ViewController,请改用此协议和扩展:

    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 中的代码),您可能会觉得这很有趣。

    【讨论】:

    • 是的,为每个 UIViewController 使用单独的协调器也让我感到奇怪。我认为最好的方法应该是为一个完整的模块设置一个协调员,例如拥有 Storyboard 引用的目的。
    • 根据作者的解释,本节解释了为什么每个 VC 应该有单独的协调器 - 何时创建协调器?
    • 我有一个 AppCoordinator、一个 MainCoordinator 和一个协调器,用于我的应用程序中的每个流。因此,对于社交应用程序,您可能有 CreatePostCoordinator、ProfileCoordinator、SettingsCoordinator 等。因此每个协调员将管理与该流程相关的所有 VC,并决定显示哪个以及何时显示。这也将这些 VC 的初始化移到了更中心的位置。您甚至可以使用 ViewControllerFactory 或类似此处使用的示例:swiftbysundell.com/posts/navigation-in-swift
    • 虽然我对这个答案深信不疑,但想在下定决心之前先征求其他人的意见。现在我有了。
    【解决方案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,提供该视图控制器的故事板并通过执行 @ 来实例化它987654328@

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

    【讨论】:

      【解决方案3】:

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

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

      关于 XIB 与故事板

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

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

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

      【讨论】:

        猜你喜欢
        • 2016-12-10
        • 2013-11-04
        • 2020-05-04
        • 2013-07-09
        • 2013-08-26
        • 1970-01-01
        • 2021-12-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多