Closed nmdias closed 7 years ago
Hi, @nmdias
To me it seems that mail flow is pretty much similar to Image picker flow. So I would probably do something similar to this example from the main repo. Sure this may sound little bit weird but IMO both of these examples are in a way pickers. I mean we do not manually build UI for these components, we just respond to the user input.
Thanks, @sergdort
That example was helpful.
I managed to adapt it to the MFMailComposeViewController
. Didn't test it thoroughly, but should do the job.
I'll leave it here for reference:
enum MFMailComposeError: CustomNSError {
case cantSendEmail
static let domain = "Custom MFMailCompose error"
var errorCode: Int { return -9999 }
var errorUserInfo: [String : Any] {
return [
NSLocalizedDescriptionKey: "Device cannot send email.",
NSLocalizedFailureReasonErrorKey: "The device doesn't have an email account setup.",
NSLocalizedRecoverySuggestionErrorKey: "Setup an email accound on the device."
]
}
}
extension Reactive where Base: MFMailComposeViewController {
/// Reactive wrapper for `delegate` message.
public var didFinishWith: Observable<(MFMailComposeResult, NSError?)> {
return delegate
.methodInvoked(#selector(MFMailComposeViewControllerDelegate.mailComposeController(_:didFinishWith:error:)))
.map { result in
let composeResult = try castOrThrow(MFMailComposeResult.self, result[1])
let error = try castOptionalOrThrow(NSError.self, result[2])
return (composeResult, error)
}
}
}
extension Reactive where Base: MFMailComposeViewController {
static func create(
with parentViewController: UIViewController?,
animated: Bool = true,
configure: @escaping (MFMailComposeViewController) throws -> () = { x in })
-> Observable<MFMailComposeViewController>
{
return Observable.create({ [weak parentViewController] observer -> Disposable in
if !MFMailComposeViewController.canSendMail() {
observer.on(.error(MFMailComposeError.cantSendEmail))
return Disposables.create()
}
let mailComposer = MFMailComposeViewController()
do {
try configure(mailComposer)
} catch let error {
observer.on(.error(error))
return Disposables.create()
}
guard let parentViewController = parentViewController else {
observer.on(.completed)
return Disposables.create()
}
parentViewController.present(mailComposer, animated: animated)
observer.on(.next(mailComposer))
return Disposables.create {
dismissViewController(mailComposer, animated: animated)
}
})
}
}
fileprivate func castOrThrow<T>(_ resultType: T.Type, _ object: Any) throws -> T {
guard let returnValue = object as? T else {
throw RxCocoaError.castingError(object: object, targetType: resultType)
}
return returnValue
}
fileprivate func castOptionalOrThrow<T>(_ resultType: T.Type, _ object: Any) throws -> T? {
if NSNull().isEqual(object) {
return nil
}
guard let returnValue = object as? T else {
throw RxCocoaError.castingError(object: object, targetType: resultType)
}
return returnValue
}
fileprivate func dismissViewController(_ viewController: UIViewController, animated: Bool) {
if viewController.isBeingDismissed || viewController.isBeingPresented {
DispatchQueue.main.async {
dismissViewController(viewController, animated: animated)
}
return
}
if viewController.presentingViewController != nil {
viewController.dismiss(animated: animated, completion: nil)
}
}
Finally:
MFMailComposeViewController.rx.create(with: self) { (composer) in
composer.setToRecipients(["mail@example.com"])
composer.setMessageBody("Message body", isHTML: false)
}
Hi,
Could you give some advice on where to place an
MFMailComposeViewController
?In a non RxSwift and non Clean Architecture project, I would implement it in some view controller, like this:
Within the Clean Architecture, where would you place the Mail Composer?
Would you present this from the Navigator/Router? It is after all a "Scene", even if we don't necessarily have a Navigator/Router and a ViewModel dedicated to the MailComposer.
There are 2 distinct places where errors might occur, and I really don't think the Navigator should handle these.
Thanks!