Reputation: 437
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
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
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)
}
}
Upvotes: 11