Hello Again, UIKit
Preamble
Yay! A technical article has appeared! Follow me in my quest to make an iMessage app. Most important takeway? The song.
Why, 'again'? And why UIKit? Since finishing my bachelor’s, I've been having the time to actually make some side projects of mine. I have some small ideas that I want to experiment with or maybe even publish. One of them involves an iMessage app. Not an extension, an actual standalone iMessage app. In it, the user should select an image and I will perform some magic before sending it to another user. That's the gist. There is more, but I fear someone will be much quicker than me in building it. Then again, this idea is neither monetizable nor revolutionary. So I might as well share it here. For WWDC, I've traveled internationally, and on the flight, they had Wi-Fi. Sadly in that Wi-Fi, you could only send text messages and no pictures. I immediately forgot about the 'no pictures' rule and tried to send a picture of the 5 tiny snack cookies I got. iMessage wasn't happy and the Wi-Fi wasn't either and kicked me out. In that moment I asked myself, 'can't I send an image as a base64 String?' and the answer is, yes, yes I can. That's the main idea of the iMessage app I'm making, just a small thing to allow me to send some images as a base64 string, and decoding it on the other end.
Storyboards
So I found myself browsing on how to make an iMessage App. I thought 'this can't be that difficult' and now here we are. When you create an iMessage app through Xcode templates, it leaves you with a Storyboard. I haven't worked with storyboards since 2021. In that time I forgot most of how to do that. I felt storyboards are dead. SwiftUI is the great new thing we should all love and use. And to be fair here, I do love SwiftUI. So much so, that I exclusively use SwiftUI since end of 2021. So for me, UIKit and Storyboards is long lost knowledge. Maybe that's a little childish, but for me it's 'Hello again'. (By the way, am I the only one that reads 'hello again' and immediately has the song playing in their head? I can only read it as the song and when I read it a piano starts playing the opening chords.)(I linked the song on Apple Music below, it's 'Hello Again' by Howard Carpendale)
Yes I could be using SwiftUI I'm sure, but I kinda do like the challenge, and my layout is simple. I need a button and a Label, and then maybe also an Image Picker and Image View.
Constraints
Pulling all of those Components (the Label and the Button) into the Storyboard was quite easy, and I totally forgot the most important part: Constraints. Basically, if you just move them into the storyboard, they will not stay in the place where you put them; you have to add constraints to keep them in place.
Also, the constraints make sure that they move with the view. I want my button to always stay at the bottom and always cover a large part of the width of the phone. The same is true for the text; I want the text to cover the full width (that is still legible) and move with that.
Now, this doesn't look too bad. But what about a blur? and padding, rounded corners and so on? The text is too far at the top, so I want to fix that.
Nice, now it's a capsule and the text is centered. The image doesn't have round corners tho, and maybe that’s for later, but I need that blur. About 2h later I ask myself 'what's for dinner? AND WHY IS MY BEAUTIFUL CAPSULE A CIRCLE NOW?' I make dinner. Then I 'Command + z' until it's a capsule again.
Maybe I should focus on the actually important part. The functionality. To do this, I open Star Trek Online (yes, I am a big Star Trek fan) and play 3 TFOs with a friend. My friend and I promise each other to go to bed now to rise and shine early tomorrow.
Naturally I open my laptop to move on to the functionality. I get a discord message about 10 minutes later. 'Look at the cool list I made!'. Rise and shine indeed.
The Message
The way I understand iMessage (extensions) is that you send a 'Message' object which has a URL with all the important info. You can create your own structs and encode the values onto that URL with Query Items. So far so simple.
/// i created this function based on an article I cannot find anymore
private func createMessage(with baseItem: Base, session: MSSession? = nil) -> MSMessage? {
var components = URLComponents()
let imageData = URLQueryItem(name: "image", value: "whatever data you have")
let layout = MSMessageTemplateLayout()
components.queryItems = [imageData]
let message = MSMessage(session: session ?? MSSession())
// if let conversation = activeConversation, let msg = conversation.selectedMessage{
// if msg.senderParticipantIdentifier == conversation.localParticipantIdentifier {
// layout.caption = "You sent an image"
// }else{
// layout.caption = "Image by: $\(msg.senderParticipantIdentifier.uuidString)"
// }
// }
layout.trailingSubcaption = "sent as base64"
message.url = components.url!
message.layout = layout
return message
}
This MSMessageTemplateLayout
also has some other nice things you can set; loads of captions and also a preview image. You can also sorta gain access to the name of the sender here. The participant identifier will then be replaced by the name the person has in their contacts. I do not use this option. This might be useful if you create an app that allows for participation on the same message, like maybe a voting tool, or a game.
When I created the project, it came with a ViewController that's inheriting from MSMessagesAppViewController
. This ViewController had a couple of functions and annotations, as to when in the life cycle they would be called. (NOTE: at this point in time I did not understand the lifecycle.)
Now. I am a bit confused as to what I should be doing with a function named didStartSending()
. There it says 'Called when the user taps the send button.' But looking at my app, where is the send button? I assume that I can add a 'send button' or make it appear some way? But where do I create and send the message? In this function?
override func didStartSending(_ message: MSMessage, conversation: MSConversation) {
// Called when the user taps the send button.
}
What I read in the documentation is that not only can you just 'send' a message directly; you can also 'insert' the message. That message is then not sent, only inserted in the conversation; it will be sent once the user presses the Send button. So there needs to be a send button somewhere.
conversation.send(message) // inserts message and waits for user to press send button
conversation.insert(message) // sends message without user needing to press send button
The Send Button
So the issue remains: there is no send button in my extension. No matter if I send or insert the message. no. send. button. The send button should be where it is when you write a text message, so at the trailing end of the text area.
Now might be the time to open Apple's sample project. Theirs somehow works. Not sure how. But I will figure it out. In the sample project, they use the insert()
function, and not the send()
function. This function will not be called in didStartSending()
, as pressing the send button will call that one, so the message needs to already exist and have been inserted into the conversation. This is when I realize 'maybe I should check for errors from my 'send' function'.
guard let convo = self.activeConversation else { return }
convo.send(message) { error in
if let error = error {
print(error)
}
}
Error Domain=com.apple.messages.messagesapp-error Code=8 "(null)"
Splendid. Google does have an AI suggestion, but it isn't helpful. what a surprise. Maybe I should try to send a message with the 'sendText(string)' function.
convo.sendText("Hello?????? I am sendinf this from an app extension.....")
It is 1 am. I was able to send a message.
My special message still doesn't send. But maybe that's an issue for tomorrow (aka later today).





