Swift高级分享 - Swift中的轻量级演示者

关注点的分离 - 每种类型理想情况下应该具有非常明确的责任区域 - 是最普遍认可的编程原则之一,但在实践中,说起来容易做起来难。

特别是在UI开发方面,明确将每种类型与其他类型分开是非常棘手的,并且看到像视图和视图控制器这样的类最终会有大量的功能和任务,这一点并不罕见 - 因为它们通常必须处理许多不同的事情,从布局到样式,再到响应用户输入。

本周,我们来看看我们如何使用演示者模式将一些任务(特别是与其他UI的呈现相关)从我们的视图控制器转移到单独的专用类型。

同时小编这里有些书籍和面试资料哦(点击下载

演示逻辑
有些UI类是非常独立的,不需要花费很多精力来呈现,例如在这个例子中 - 我们将一个简单的东西推ProfileViewController到导航堆栈上:

let vc = ProfileViewController(user: user)
navigationController?.pushViewController(vc, animated: true)

但是,某些类型在使用它们之前需要更多的设置 - 这不一定是坏事 - 它可以表明类型的强大和可定制性。

其中一种类型是UIAlertControlleriOS 8中引入的更强大 - 也更复杂 - 替代更简单,更有限的类型UIAlertView。现在,UIAlertController显示系统警报需要UIAlertAction为每个警报按钮添加一个,并配置闭包以处理选择。

让我们看一下如何使用表示模式来封装这样的表示逻辑和设置。虽然存在设计模式,其中演示者扮演更重要的角色 - 例如在MVP(模型视图演示者)模式中 - 在这种情况下,我们将以更轻量级的方式使用演示者,并简单地将它们定义为负责的类型呈现某个UI。

假设我们要显示警报视图以让用户确认操作 - 例如删除项目。我们不是让呈现视图控制器本身配置警报视图,而是将所有代码封装在一个中ConfirmationPresenter,如下所示:

struct ConfirmationPresenter {
    /// The question we want the user to confirm
    let question: String
    /// The title of the button to accept the confirmation
    let acceptTitle: String
    /// The title of the button to reject the confirmation
    let rejectTitle: String
    /// A closure to be run when the user taps one of the
    /// alert's buttons. Outcome is an enum with two cases:
    /// .accepted and .rejected.
    let handler: (Outcome) -> Void

    func present(in viewController: UIViewController) {
        let alert = UIAlertController(
            title: nil,
            message: question,
            preferredStyle: .alert
        )

        let rejectAction = UIAlertAction(title: rejectTitle, style: .cancel) { _ in
            self.handler(.rejected)
        }

        let acceptAction = UIAlertAction(title: acceptTitle, style: .default) { _ in
            self.handler(.accepted)
        }

        alert.addAction(rejectAction)
        alert.addAction(acceptAction)

        viewController.present(alert, animated: true)
    }
}

在这种情况下,我们使用结构来定义我们的演示者 - 主要是因为它不必管理任何状态,并且因为它还使我们能够利用Swift的自动生成的结构初始化器。有了上述内容,我们现在可以在任何视图控制器中超级轻松地显示确认警报,如下所示:

class NoteViewController: UIViewController {
    func handleDeleteButtonTap() {
        let presenter = ConfirmationPresenter(
            question: "Are you sure you want to delete this note?",
            acceptTitle: "Yes, delete it!",
            rejectTitle: "Cancel",
            handler: { [unowned self] outcome in
                switch outcome {
                case .accepted:
                    self.noteCollection.delete(self.note)
                case .rejected:
                    break
                }
            }
        )

        presenter.present(in: self)
    }
}

上述方法的优点在于它可以让我们轻松地重用我们的演示代码而无需任何子类UIAlertController,或者使用类似扩展的东西UIViewController- 这将增加向所有视图控制器显示确认警报的功能,甚至是那些真正不喜欢的视图控制器不需要它。

我们现在可以简单地创建一个演示者,并present()在我们想要显示确认警报时调用它。

包装好了
演示者还可以提供一种很好的方法来确保以正确的方式呈现某些视图控制器。在进行模态演示时,需要将一些视图控制器包装在a中UINavigationController,以支持在新模态堆栈中进一步导航,并且如果在多个位置呈现相同的视图控制器,则很容易忘记。

让我们看看另一个例子,其中我们使用a MovieListViewController显示用户最喜欢的电影列表。我们的应用程序在许多不同的地方显示电影列表,因此我们已经MovieListViewController非常灵活地支持所有这些用例 - 通过UITableViewDataSource根据上下文启用注入。

要包含所有设置代码,以及确保我们MovieListViewController在导航控制器中正确包装,让我们再次使用演示者 - 这次我们将调用它FavoritesPresenter:

struct FavoritesPresenter {
    let manager: FavoritesManager

    func present(in viewController: UIViewController) {
        let dataSource = FavoritesDataSource(manager: manager)
        let list = MovieListViewController(dataSource: dataSource)
        let navigationController = UINavigationController(rootViewController: list)

        list.navigationItem.leftBarButtonItem = UIBarButtonItem(
            barButtonSystemItem: .done,
            target: navigationController,
            action: #selector(UIViewController.dismissWithAnimation)
        )

        viewController.present(navigationController, animated: true)
    }
}

上面的UIViewController.dismissWithAnimation方法是一个简单的包装器dismiss(animated:completion:),以便能够将它用作Objective-C选择器。

就像我们ConfirmationPresenter之前一样,我们FavoritesPresenter使调用站点非常干净 - 并且让我们减少了将这种表示逻辑放在任何视图控制器中的需要:

class HomeViewController: UIViewController {
    func presentFavorites() {
        let presenter = FavoritesPresenter(manager: favoritesManager)
        presenter.present(in: self)
    }
}

在这种情况下,这种方法的另一个好处是,HomeViewController(和所有其他视图控制器呈现收藏夹)不必知道所呈现的视图控制器被包含在UINavigationController- 这为我们提供了更大的灵活性,我们希望将来转向另一种导航范例。

排队的演讲
让我们来看看轻量级演示者如何真正有用的最后一个例子。在这种情况下,我们想要提供多个小教程,每个教程都向用户讲授我们应用程序的不同方面。为了使代码库的多个部分能够呈现教程而不必担心教程是否已经呈现,我们创建了一个演示者来管理所有状态和逻辑。

这就是这样一位主持人的样子。在这种情况下,我们将使用一个类,因为我们需要跟踪当前呈现的视图控制器,以及即将到来的教程项的队列:

class TutorilPresenter {
    private let presentingViewController: UIViewController
    private weak var currentViewController: UIViewController?
    private var queue = [TutorialItem]()

    init(viewController: UIViewController) {
        presentingViewController = viewController
    }

    func present(_ item: TutorialItem) {
        // If we're already presenting a tutorial item, we'll
        // add the next item to the queue and return.
        guard currentViewController == nil else {
            queue.append(item)
            return
        }

        let viewController = TutorialViewController(item: item)

        viewController.completionHandler = { [weak self] in
            self?.currentViewController = nil
            self?.presentNextItemIfNeeded()
        }

        presentingViewController.present(viewController, animated: true)
        currentViewController = viewController
    }

    private func presentNextItemIfNeeded() {
        guard !queue.isEmpty else {
            return
        }

        present(queue.removeFirst())
    }
}

我们现在可以tutorialPresenter.present()从我们想要提供教程的任何地方打电话,我们的演示者将负责其余的工作!?

结论

演示者模式可以非常强大,即使它以非常轻量级的方式使用。通过将我们所有的表示逻辑封装到专用类型中,我们最终可以获得更容易重用的代码,并且由于我们的演示者具有如此狭窄的范围 - 他们只管理视图或视图控制器的呈现 - 它们是通常也很容易维护。

当然还有许多其他方法可以在Swift中使用演示者,包括完全采用模型视图演示器等设计模式- 但是通过这种轻量级方法,我们可以利用演示者而无需将所有代码移动到一种新的设计模式。另一方面,我们在我们的应用程序中引入了一个新的抽象或架构概念 - 因此,为了避免混淆,明确定义我们希望像演示者一样在我们的应用程序中扮演什么样的角色非常重要。

你怎么看?您之前是否使用过这样的演示者模式,或者您将尝试使用它?请通过加我们的交流群 点击此处进交流群 ,来一起交流或者发布您的问题,意见或反馈。

谢谢阅读~点个赞再走呗!?

原文地址 https://www.swiftbysundell.com/posts/lightweight-presenters-in-swift

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值