The basic premise behind Clean Architecture is that the "business rules" i.e., the logic of your app, is not dependent on, or executed by, the UI. Instead the Logic of your app is in control.
This means that some part of the logic of the app knows when the user can send an email, but it has no idea exactly how that happens.
If you are using RxSwift, you could think of the user interaction as a model transformation. So your example would become:
func sendMail(recipients: [String], tile: String, message: String, isHTML: Bool) -> Observable<Bool>
The above could be passed to your logic as a closure or it could be embedded in a protocol that your logic uses.
If you want to use Robert Martin's specific structure, then things are a bit different because you wouldn't use Rx in your model objects at all. (He recommends that your Interactors &al. don't depend on outside libraries.)
In which case, an Interactor would send a message to the presenter to display the email view controller through a Response Model object and the Controller would send the success/failure result back to the Interactor, or more likely to a different Interactor.
Here is how Uncle Bob says he structures things: https://camo.githubusercontent.com/c34f4ed0203238af6e43b44544b864dffac6bc08/687474703a2f2f692e696d6775722e636f6d2f576b42414154792e706e67 However in the one iOS Swift app he has publicly presented, he didn't use this structure. https://github.com/unclebob/MACS_GOMOKU
To elaborate after your comment, the signature does work, but it requires some supporting structure...
First, a nice to have but not strictly necessary piece, we make view controller presentation reactive:
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()
}
}
}
It's not just that a view controller can only be presented by another view controller, but also that it must be the one view controller in the system that isn't currently presenting anything. We can find that view controller by starting at the root and walking up the presentation stack:
extension UIViewController {
static func top() -> UIViewController? {
var result = UIApplication.shared.delegate.flatMap { $0.window??.rootViewController }
while let child = result?.presentedViewController {
result = child
}
return result
}
}
Now, instead of having some view controller conform to the MFMailComposeViewControllerDelegate
protocol, we make a dedicated Reactive class.
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)
}
}
}
Once all these pieces are in place, writing the sendMail function is easy:
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 }
}
And like I said, you should not call this function directly. Instead you should inject it into the object that will call it so that you can mock it out for testing.
This same pattern works for UIImagePickerController and even UIAlertController!
You might find this article I wrote an interesting read. It uses promises instead of Rx, but the philosophy is the same: https://medium.com/@danielt1263/encapsulating-the-user-in-a-function-ec5e5c02045f