Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending SMS programmatically using SwiftUI

Tags:

ios

swift

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'
like image 764
Navan Chauhan Avatar asked Dec 06 '22 08:12

Navan Chauhan


2 Answers

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

like image 109
Jawad Ali Avatar answered Jan 03 '23 10:01

Jawad Ali


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)")
            }
        }
    }
}
like image 31
Aaron T Avatar answered Jan 03 '23 09:01

Aaron T