1

So I have a class that has a function to send an SMS message. I tried making the class conform to the delegate protocol...it ends up causing the class to require a type alias which I thought would be of type MFMessageViewController but that didn't work

class functions: NSObject, ObservableObject, MFMessageComposeViewControllerDelegate {
    func sendInviteMessage(number: String) {
        if MFMessageComposeViewController.canSendText() {
            let message = MFMessageComposeViewController()
            message.messageComposeDelegate = self
            message.recipients = [number]
            message.body = "Hi, I'd like to invite you to join my app google.com"
        
            message.present(message, animated: true)
        }
    }

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        controller.dismiss(animated: true)
    }
}
nickcoding
  • 305
  • 8
  • 35

2 Answers2

2

I think you should have a look here to get deeper insights of delegation and protocols in swift. You cannot instantiate a protocol - you need a class that conforms to that protocol to accomplish what you want.

I think your class should look like this:

class functions: NSObject, ObservableObject, MFMessageComposeViewControllerDelegate {
    
    func sendInviteMessage() {
    
        if MFMessageComposeViewController.canSendText() {
            let message = MFMessageComposeViewController()
            message.messageComposeDelegate = self
            message.recipients = ["##########"]
            message.body = "Hi, I'd like to invite you to join my app google.com"

            // here we have a problem - You will need a ViewController or something that presents the MessageComposeViewController "message" --> It cannot present itself
            //message.present(message, animated: true)
        }
    }
    
    func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
        controller.dismiss(animated: true)
    }
}

It highly depends on your needs how your solution for the presentation looks like. (IMO this code should be placed in a UIViewController)

Let me know if it helps!

Teetz
  • 3,475
  • 3
  • 21
  • 34
  • I figured it out right before you sent in this answer, but I get a weird error that says 'Application tried to present modal view controller on itself' when I try to call the sendInviteMessage() function...any ideas? – nickcoding Feb 14 '21 at 15:21
  • 1
    I edited my answer - it should be clearer now – Teetz Feb 14 '21 at 15:50
  • I understand that--I'm working in SwiftUI if that helps though which is not MVC...Should I just be making two MFMessageComposeViewControllers and then have one present the other? Because the issue is that message is presenting itself, right? – nickcoding Feb 14 '21 at 15:59
  • 1
    If you are working with SwiftUI the visible View is responsible for presenting and dismissing the Controller. You will have to wrap the MessageController inside UIViewControllerRepresentable-Protocol. Have a look here on how to accomplish that: https://medium.com/@Johannes_Nevels/presenting-uiviewcontrollers-in-swiftui-22388616a24c There are a lot of tutorials out there. – Teetz Feb 14 '21 at 16:03
  • So do I just make the functions protocol conform to that protocol? If I do that, it ends up causing the class to require a type alias which I thought would be of type MFMessageViewController but that didn't work... – nickcoding Feb 14 '21 at 16:51
1

If your project use SwiftUI then you can use UIViewControllerRepresentable

struct MessageComposeViewController: UIViewControllerRepresentable {
    
    var toRecipients: [String]
    var messageBody: String
    
    var didFinish: ()->()
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<MessageComposeViewController>) -> MFMessageComposeViewController {
        
        let message = MFMessageComposeViewController()
        message.messageComposeDelegate = context.coordinator
        message.recipients = self.toRecipients
        message.body = self.messageBody
        
        return message
    }
    
    final class Coordinator: NSObject, MFMessageComposeViewControllerDelegate {
        
        var parent: MessageComposeViewController
        
        init(_ controller: MessageComposeViewController) {
            self.parent = controller
        }
        
        func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
            parent.didFinish()
            controller.dismiss(animated: true)
        }
    }
    
    func updateUIViewController(_ uiViewController: MFMessageComposeViewController, context: UIViewControllerRepresentableContext<MessageComposeViewController>) {
        
    }
}

usage:

struct MessageView: View {
    
    @State private var showingMessage = false
    
    var body: some View {
        VStack {
            Button("Open Message") {
                self.showingMessage.toggle()
            }
        }
        .sheet(isPresented: $showingMessage) {
            MessageComposeViewController(toRecipients: ["1234567890"], messageBody: "Hi, I'd like to invite you to join my app google.com") {
                // Did finish action
            }
        }
    }
}


Possible another solution. You can create one singleton class and present MFMessageComposeViewController on the root controller. Like this

class Functions: NSObject, MFMessageComposeViewControllerDelegate {

    static let shared = Functions()
    
    func sendInviteMessage(number: String) {
        if MFMessageComposeViewController.canSendText() {
            
            let message = MFMessageComposeViewController()
            message.messageComposeDelegate = self
            message.recipients = [number]
            message.body = "Hi, I'd like to invite you to join my app google.com"
            
            UIApplication.shared.windows.first?.rootViewController?.present(message, animated: true)
        }
    }
    
    func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
        controller.dismiss(animated: true)
    }
}

usage:

struct MessageView: View {
    var body: some View {
        Button("Open Message") {
            Functions.shared.sendInviteMessage(number: "12345678")
        }
    }
}
Raja Kishan
  • 16,767
  • 2
  • 26
  • 52