Search code examples
swiftdelegatesuikitmfmessagecomposeviewcontroller

Delegate cannot be constructed because it has no accessible initializers?


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)
    }
}

Solution

  • 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")
            }
        }
    }