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'
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)
}
}
inspiration and credits
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)")
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With