Sending a SMS with SwiftUI

Ethan Bonin
6 min readFeb 12, 2021

--

I recently built the 4th version of my iOS application called
Text Me That.

It is a rather simple app that sends scheduled text message reminders.

I rebuilt the latest version of Text Me That from the ground up using Apple’s SwiftUI (2.0) framework.

The Problem.

There is a feature inside Text Me That that allows you to share reminders with friends and family. I call this group reminders.

After successfully creating a reminder, the user can easily share the reminder via text message.

In UIKit: TRIVIAL PROBELM.

It was as simple as presenting the MFMessageComposeViewController, then listening to the call backs to see if the text message was successfully sent.

In SwiftUI: A REAL PROBLEM.

You would think you could easily follow this answer on Slack Overflow. Let’s go through the two main answers before continuing how I solved this issue for myself. I tried both of these solutions for some time before realizing, it could be better.

FIRST ANSWER:

works. but with headaches.

Create an extension to the view, create an object that can hold the MFMessageComposeViewControllerDelegate, then in the content view, create a variable holding that object.

The 2 problems with this.

1. DRY. Don’t Repeat Yourself

With this solution, you would need to create an extension on every view that you want to present the MessageComposeController.

This is a problem. What if you change the implementation in one place, and then forgot to change it in the other 12? Boom baby. Welcome to bug city.

2. Presentation View Hell.

This one line of code is the bane of my existence in SwiftUI. I’m not going to get into all the technical details on why this is wrong to use, but I’ll give a very common scenario that would make this obviously painful.

When using SwiftUI, we no longer have access to the self.present(ViewController, animated: Bool) function.

This function is attached to a UIViewController object.

Hence, as you see above, we try to gain access to the rootViewController by calling the shared windows (UIApplication.shared.windows.filter) and trying to get the key window ($0.isKeyWindow). I then present the MFMessageComposeController by using that newly obtained vc.

This works well IF you only have 1 view controller in the view. Anyone that has built a simple app can probably tell you that only having a single vc is extremely unlikely.

For example: You have your main dashboard then you need to present a second view with details, and then in that detail view, you have a share button that opens up SMS.

(If you want a more concrete example, of multiple views, you can download Text Me That from the app store and create reminder then tap it to see more details.)

This is common scenario. So what happens when you do this?

As you can tell in the gif below, it can not open up messages. The key window is underneath the detail view that is being presented. It throws this error.

Attempt to present <MFMessageComposeViewController> on <ModifiedContentVS_7AnyView> (from <AnyViewVS_12RootModifier>)which is already presenting <PresentationHostingControllerVS_7AnyView>.

Sure, you could try to FIND the detail view controller that is presented. In my opinion, this is fighting against the grain of SwiftUI.

SECOND ANSWER:

Way Better — but impossible to debug 2 problems.

The second answer was to create a UIViewControllerRepresentable and place the MFMessageComposeViewController inside there.

From there, you can present the Messages UI by normal SwiftUI ways.

Pretty sexy right? I used this inside Text Me That v4 during beta but my testers kept coming back with a non-reproducible bug. When they would press the share button to send out a reminder to friends, I would present the Messages UI to make it easily textable. For some reason, sometimes the messages UI would not appear. Other times, parts of the view was disappear. OH. AND STILL OTHER TIMES, you could not dismiss the MFMessageComposeViewController. It would be stuck.

I tried many solutions. Here is a few of them.

I tried putting delay before presenting the view, just incase it was a race condition.

I tried dismissing all the way back to the root view controller before presenting.

I quadrupled check to make sure it was presented on the main thread.

By this point, I was in despair.

I was trying to fix a bug that I could BARELY get to reproduce.

I mean, I was developing the app, surely I would see this bug more often than other people? Nope.

Back to the drawing board.

I knew I wanted to use some version of answer 2 to keep my app clean and easy to code and debug.

Now there wasn’t an issue when I presented a normal view controller with UIViewControllerRepresentable.

Obviously UIViewControllerRepresentable was made in mind to easily transfer your UIKit ViewControllers to SwiftUI.

For some reason, UIViewControllerRepresentable had a hard time with MFMessageComposeViewController. I had a thought that MAYBE if I put the MFMessageComposeViewController inside a normal UIViewController that is being presented by a UIViewControllerRepresentable, I might have a chance to create something that works for me.

By placing the MFMessageComposeViewController inside a UIViewController that would mean I would have DIRECT access to the presenting view controller and not have to do any bull turds such as

to get the view controller.

It would be as simple as:

self.present(composeVC, animated: true, completion: nil) 😭

The first thing I did was create a classic UIViewController to handle the MessagesUI.

I then created a protocol to listen to when the MFMessageComposeViewControllerDelegate was called and returned that back to me.

Because the UIViewController was presenting the MFMessageComposeViewController, I wanted the UIViewController to finish loading before continuing. I created a function called displayMessageInterface() that would present the composeVC when I was ready to show it.

The finish ViewController would look like this:

Now that I have my custom view controller that handles the MFMessageComposeViewController, I could move forward with creating the UIViewControllerRepresentable.

Let’s talk about the Coordinator before continuing.

I created a protocol called MessagesViewDelegate that is inherited by the Coordinator object. MessagesViewControllerDelegate tells me when MessagesViewController is finished. I can dismiss the view by calling the parent and pass the result to whatever view needs it.

My actual UIViewControllerRepresentable looks like this:

As you can see, when makeUIViewController is called is when all the magic happens.

The completion variable is so that I can have a variable call back on what happened for the user.

Using this method has been solid for me. No bugs. It is easily used everywhere in my app.

The Final Result

Conclusion:

I do not think this is permanent solution for sending messages. Hopefully apple will come out with a future implementation for MessagesUI in the future.

I just have found a solution that has worked for me and my app. It solves for DRY. It also solves gitchy interfaces.

The picture below is the final result of the two classes.

--

--

Ethan Bonin

I am an independent software developer working on amazing things that I love.