Navan Chauhan
Navan Chauhan

Reputation: 437

Sending SMS programmatically using SwiftUI

In Swift I would do this to display a precomposed message

let composeVC = MFMessageComposeViewController()
composeVC.messageComposeDelegate = self


composeVC.recipients = ["9999999999"]
composeVC.body = "Text Message"


if MFMessageComposeViewController.canSendText() {
    self.present(composeVC, animated: true, completion: nil)
}

In SwiftUI this throws the error

Cannot assign value of type 'ContentView' to type 'MFMessageComposeViewControllerDelegate?'

and

Value of type 'ContentView' has no member 'present'

Upvotes: 9

Views: 6605

Answers (2)

Aaron T
Aaron T

Reputation: 179

The best way to present this view controller (or most any UIViewController) from SwiftUI is by making a struct using the UIViewControllerRepresentable protocol, then show it using a sheet() call in your view builder.

// MessageComposeView.swift

import MessageUI
import SwiftUI

struct MessageComposeView: UIViewControllerRepresentable {
    typealias Completion = (_ messageSent: Bool) -> Void

    static var canSendText: Bool { MFMessageComposeViewController.canSendText() }
        
    let recipients: [String]?
    let body: String?
    let completion: Completion?
    
    func makeUIViewController(context: Context) -> UIViewController {
        guard Self.canSendText else {
            let errorView = MessagesUnavailableView()
            return UIHostingController(rootView: errorView)
        }
        
        let controller = MFMessageComposeViewController()
        controller.messageComposeDelegate = context.coordinator
        controller.recipients = recipients
        controller.body = body
        
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(completion: self.completion)
    }
    
    class Coordinator: NSObject, MFMessageComposeViewControllerDelegate {
        private let completion: Completion?

        public init(completion: Completion?) {
            self.completion = completion
        }
        
        public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
            controller.dismiss(animated: true, completion: nil)
            completion?(result == .sent)
        }
    }
}

struct MessagesUnavailableView: View {
    var body: some View {
        VStack {
            Image(systemName: "xmark.octagon")
                .font(.system(size: 64))
                .foregroundColor(.red)
            Text("Messages is unavailable")
                .font(.system(size: 24))
        }
    }
}

Once you have that defined, you can use it like this

struct MyView: View {
    @State private var isShowingMessages = false
    var body: some View {
        Button("Show Messages") {
            self.isShowingMessages = true
        }
        .sheet(isPresented: self.$isShowingMessages) {
            MessageComposeView(recipients: ["recipients go here"], body: "Message goes here") { messageSent in
                print("MessageComposeView with message sent? \(messageSent)")
            }
        }
    }
}

Upvotes: 10

Jawad Ali
Jawad Ali

Reputation: 14397

You need to present over Windows rootview .Use these extension

import SwiftUI
import MessageUI
/// Main View
struct ContentView: View {

    private let mailComposeDelegate = MailComposerDelegate()

    private let messageComposeDelegate = MessageComposerDelegate()

    var body: some View {
        VStack {
            Spacer()
            Button(action: {
                self.presentMailCompose()
            }) {
                Text("email")
            }

            Spacer()

           Button(action: {
               self.presentMessageCompose()
           }) {
               Text("Message")
           }
            Spacer()
        }
    }
}

// MARK: The email extension

extension ContentView {

    private class MailComposerDelegate: NSObject, MFMailComposeViewControllerDelegate {
        func mailComposeController(_ controller: MFMailComposeViewController,
                                   didFinishWith result: MFMailComposeResult,
                                   error: Error?) {

            controller.dismiss(animated: true)
        }
    }
    /// Present an mail compose view controller modally in UIKit environment
    private func presentMailCompose() {
        guard MFMailComposeViewController.canSendMail() else {
            return
        }
        let vc = UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
        let composeVC = MFMailComposeViewController()
        composeVC.mailComposeDelegate = mailComposeDelegate

        vc?.present(composeVC, animated: true)
    }
}

// MARK: The message extension

extension ContentView {

    private class MessageComposerDelegate: NSObject, MFMessageComposeViewControllerDelegate {
        func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
            // Customize here
            controller.dismiss(animated: true)
        }
    }
    /// Present an message compose view controller modally in UIKit environment
    private func presentMessageCompose() {
        guard MFMessageComposeViewController.canSendText() else {
            return
        }
        let vc = UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
        let composeVC = MFMessageComposeViewController()
        composeVC.messageComposeDelegate = messageComposeDelegate

        vc?.present(composeVC, animated: true)
    }
}

enter image description here

inspiration and credits

Upvotes: 11

Related Questions