Sending a Message

Preamble

Today I want to figure out how to send and receive messages.

The first thing I notice when looking at my message composition is: what does the session actually do? I have to say, this is part of a sample code and I didn't really understand it, but thought at first that a session is necessary. But maybe the session needs to be created for every new message? To test this I removed the code to use the current message's session to create a new one.

/// I removed the session parameter
fileprivate func composeMessage(with baseItem: Base) -> MSMessage? {
    var components = URLComponents()

    // This is where i will apend my base64 string, but i do not do that right now to avoid that the base64 string is what crashes the inserting
    let imageData =  URLQueryItem(name: "image", value: "whatever data you have")

    let layout = MSMessageTemplateLayout()
    components.queryItems = [imageData]

    // now I use a brand new session every time
    let message = MSMessage(session: MSSession())

    layout.trailingSubcaption = "sent as base64"
    message.url = components.url!
    message.layout = layout

    return message
}

I can now send messages. YAY!!

Getting The Message

To make sure that the message isn't sending because of a payload I added, I had removed all the contents, so for now it's just an empty message with a subcaption. But this is about to change. I now need to add the content of my message at the end of the URL.

I also want to try inserting the message, in hopes that I could then have a send button.

Also, as a sub-issue, somehow an AppIcon appeared on my homescreen. Its label is my iMessage app's name. But I'm not sure why it has a representation on my homescreen. That icon was also gone this morning, but has now reappeared (without me doing anything). This isn't super important but something that confused me a bit.

SOOOOOO. I figured out why I can't send my Message. My image is too big, so after scaling it down A LOT, I was able to send it. There seems to be a limit as to how many characters I can append to the URL, so I had to scale it down and then also compress it.

guard let convo = self.activeConversation else { return }
guard let message = self.composeMessage(with: baseStringICreatedEarlier) else { return }
convo.insert(message) { error in
    if let error = error {
        print(error)
    }
}

In other news, when inserting a message into the conversation, you do get the send button! So I just need to insert() the message and then the system send button appears. And once the user presses that, 'didStartSending() actually gets called, where I can dismiss() and terminate the app. (I only do that once the user sends the image, so the user can select a different image otherwise.) Earlier, I was thinking that didStartSending() is where you send()the message, but this is not the case.

override func didStartSending(_ message: MSMessage, conversation: MSConversation) {
    // Called when the user taps the send button.
    dismiss() // dismiss so that the user sees their keyboard again, i could also reset the view to the first state
}

With that out of the way, I can focus on receiving the image once it's been sent.

At first I thought that didReceive() was the function I needed to implement when I wanted to display a UI to the recipient. This is not the case. didReceive() actually gets called when the message appears in the chat.

override func didReceive(_ message: MSMessage, conversation: MSConversation) {
    // Called when a message arrives that was generated by another instance of this
    // extension on a remote device.
    // Use this method to trigger UI updates in response to the message.
}

Actually, what I figured is the right point for my app is when willTransition() gets called. When a user taps on one of the messages I sent, the app will transition in a full height sheet and display my app's UI. So in that function, I check if there is a selectedMessage in the activeConversation. Both of these properties are optional by the way. The 'activeConversation' is defined on the MSMessagesAppViewController and has a value when the user is currently in a chat (as opposed to the chat list). The selectedMessage is the message a user clicked on. So when I click on a message I sent or received, that will be the selectedMessage. I can simply check if there is a message, and then display the image only whilst hiding the rest of the UI. And when there isn't a message, the user wants to create a new one, so I hide the (empty) image view and have the other UI appear. I do not have to do this at apps entrypoint, which is willBecomeActive(). willTransition() will always get called, whilst willBecomeActive() will only get called when the app is starting. If the user had the app already open in their chat, then it's not getting called (again) and they won't be seeing the sent image, they will simply still see their instance of the app.

override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
    // Called before the extension transitions to a new presentation style.
    // Use this method to prepare for the change in presentation style.
    
    // this is where i decided to look if i recieved a message. I know this isnt how this was meant
    if let message = activeConversation?.selectedMessage {
        // i then decode the message, hide the button and the blur and show the image
    }
}

When the user decided to not send my message (press the little x icon on the right side), didCancelSending() gets called. In that case, I decided to simply remove the selected image from the ImageView and let the user see the basic setup UI again, so they can start over.

override func didCancelSending(_ message: MSMessage, conversation: MSConversation) {
    // Called when the user deletes the message without sending it.
    // Use this to clean up state related to the deleted message.
}

Final Remarks

Today is Saturday, and I'm trying to make a small TestFlight to share my app with some friends. Maybe I will even publish it once I've made some touch-ups. The day started with a minor mishap on my part, where I accidentally permanently deleted the whole app and had to Time Machine it back into existence. I also created a small App Icon, and it will not show up on my phone, but on the Simulator. (Note: it now shows up on my phone, but when uploading to App Store Connect, all I get is 'iMessage icon missing' as the error.)

I was debating some names with a friend and we had some great ideas, but I think that's why they were all already taken. One I could come up with was AisleMessage, which isn't directly planes, but I do like the pun and hope App Review likes it as well.

But with this, there are still some issues for me to solve, and maybe I will at some point in time.
1. How do I actually publish this? This isn't an app, it's a standalone iMessage app, and Apple's documentation on how to publish this is kinda sparse. (Even if not for this exact app, I would like to know this)
2. Can I increase the quality? I would like it if the images were a little better quality than they are right now.
3. How do I round the corners on an ImageView?
4. How do I test this? I am not on a plane, and I'm not sure if I can send a message from an iMessage app while on the Wi-Fi.

All in all it was quite fun making this, mainly because it was very small and constrained. Actually, all my code is in one file! I think I would like to repeat this in the future. I learned a lot but I still haven't really figured this out, if you have any experience with this, I'd love to hear from you!

Tagged with: