从哪里可以在Clean Architecture中展示MFMailComposeViewController?

问题描述:

你可以给出一些建议,在哪里可以放置MFMailComposeViewController从哪里可以在Clean Architecture中展示MFMailComposeViewController?

在非RxSwift和非清洁建筑项目,我会实现它在某些视图控制器,这样的:

extension ViewController: MFMailComposeViewControllerDelegate { 

    func presentMailComposer() { 

     if !MFMailComposeViewController.canSendMail() { 
      // TODO: - Handle error here 
      return 
     } 

     DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async { 

      let mailComposeViewController = MFMailComposeViewController() 
      mailComposeViewController.mailComposeDelegate = self 
      mailComposeViewController.setToRecipients(["[email protected]"]) 
      mailComposeViewController.setMessageBody("Message body", isHTML: false) 

      DispatchQueue.main.async(execute: { 
       self.present(mailComposeViewController, animated: true, completion: nil) 
      }) 

     } 

    } 

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { 
     if result == MFMailComposeResult.failed { 
      // TODO: - Handle error here 
     } 
    } 

} 

在清洁建筑,在那里你会放在邮件作曲家?

你会从导航器/路由器中提出这个吗?毕竟它是一个“场景”,即使我们不一定有专用于MailComposer的导航器/路由器和ViewModel。

有2个不同的地方可能会发生错误,我真的不认为导航器应该处理这些。

谢谢!

Clean Architecture的基本前提是“业务规则”即应用程序的逻辑不依赖于UI,或由UI执行。相反,您的应用程序的逻辑是在控制之下。

这意味着应用程序的逻辑的某些部分知道用户可以发送电子邮件,但不知道究竟如何发生。

如果您使用RxSwift,您可以将用户交互视为模型转换。因此,您的示例将变为:

func sendMail(recipients: [String], tile: String, message: String, isHTML: Bool) -> Observable<Bool> 

上面的代码可以作为闭包传递给您的逻辑,也可以嵌入您的逻辑使用的协议中。


如果你想使用罗伯特·马丁的具体结构,那么事情就有点不一样,因为你不会使用RX在你的模型对象都没有。 (他建议您的Interactors &等不依赖于外部库。)

在这种情况下,Interactor将向主讲者发送消息以通过响应模型对象显示电子邮件视图控制器,而Controller将会将成功/失败结果发送回Interactor,或更可能发送给不同的Interactor。

下面是Bob叔叔说他是如何构造的东西:https://camo.githubusercontent.com/c34f4ed0203238af6e43b44544b864dffac6bc08/687474703a2f2f692e696d6775722e636f6d2f576b42414154792e706e67但是,在他公开展示的一个iOS Swift应用中,他没有使用这种结构。https://github.com/unclebob/MACS_GOMOKU


为了详细说明您的评论后,签名的工作,但它需要一些支撑结构...

首先,一个不错的,但不是绝对必要的一块,我们做的视图控制器演示功:

extension Reactive where Base: UIViewController { 

    func present(_ viewControllerToPresent: UIViewController, animated: Bool) -> Observable<Void> { 
     return Observable.create { observer in 
      self.base.present(viewControllerToPresent, animated: animated, completion: { 
       observer.onNext() 
       observer.onCompleted() 
      }) 
      return Disposables.create() 
     } 
    } 
} 

这不只是一个视图控制器只能由另一视图控制器呈现,而且它必须是当前未呈现任何系统中的一个视图控制器。我们可以通过从根开始,并走上了演讲堆栈发现视图控制器:

extension UIViewController { 

    static func top() -> UIViewController? { 
     var result = UIApplication.shared.delegate.flatMap { $0.window??.rootViewController } 
     while let child = result?.presentedViewController { 
      result = child 
     } 
     return result 
    } 
} 

现在,而不是有一些视图控制器符合MFMailComposeViewControllerDelegate协议,我们做了专门的无类。

class MailComposeViewControllerDelegate: NSObject, UINavigationControllerDelegate, MFMailComposeViewControllerDelegate { 

    let subject = PublishSubject<MFMailComposeResult>() 

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { 
     if let error = error { 
      subject.onError(error) 
     } 
     else { 
      subject.onNext(result) 
     } 
    } 
} 

一旦所有这些作品都到位,写sendmail的功能很简单:

func sendMail(recipients: [String], tile: String, message: String, isHTML: Bool) -> Observable<MFMailComposeResult> { 
    let delegate = MailComposeViewControllerDelegate() 
    let controller = MFMailComposeViewController() 
    controller.delegate = delegate 
    return UIViewController.top()!.rx.present(controller, animated: true) 
     .flatMap { delegate.subject } 
} 

就像我说的,你应该调用这个函数直接。相反,您应该将其注入到将调用它的对象中,以便您可以将其嘲笑为测试。

这种模式适用于UIImagePickerController,甚至UIAlertController!

你可能会发现这篇文章我写了一篇有趣的阅读。它使用承诺而不是Rx,但哲学是相同的:https://medium.com/@danielt1263/encapsulating-the-user-in-a-function-ec5e5c02045f

+0

我不认为签名会起作用。我不知道如何。然而!关于MFMailComposeViewController的事情是它必须从现有的视图控制器启动。只有现有的视图控制器和我的路由器/交互器了解视图控制器。如果'sendMail'函数被用作一个用例,在我的域中定义为一个接口/协议,我不知道我该如何呈现它。现在去海滩吧!当我回来时我会再次选择你的答案。看起来很有希望。谢谢丹尼尔。 – nmdias

+0

我添加到我的答案来解释如何实现该功能。 –

+0

谢谢@Daniel。在此期间我试图在RxSwift中添加对MFMailComposeViewController的支持。我将把它留在这里作为参考。 https://github.com/ReactiveX/RxSwift/issues/1378 – nmdias

这取决于你决定管理你的项目的方式。

最终邮件撰写元素是一个UI元素,所以提出应该在UI处理类进行 - 比如你的VC,某种扩展名的喜欢你发等。

在我看来,是什么你可以做的是子类化你的邮件编辑器,并在它完成时创建一个完成块响应,然后在UI中相应地处理错误,这样它将自己管理(由于它是一个全局控制器,具有一个普通的VM因为这是浪费代码)。

然后,当您提交邮件编辑器时,您让用户添加完成和失败块/使用来自Rx的信令返回结果。