Using iOS Notifications, Cryptography and iCloud to build your own Chat App IV

I should start this article with a disclaimer, it based on iOS 13, Swift 5 and Xcode 11.x. If you reading this and those numbers look dated, be forewarned.

I should also warn you that notifications and iCloud code, involve Apple’s infrastructure which means you will need an Apple Developers account to use them.

Finally Obviously this is part IV, you need to go back to part I, part II & part II for it to make any sense, indeed you need to look at the notifications series before do that else your struggle.

We’re almost ready for another test. You just need to make a few more changes. Start with ContentView.swift and add this code.

TextField("Message", text: $yourMessageHere, onCommit: {
self.output = self.yourMessageHere
cloud.fetchRecords(name: self.sendingTo!)
}).onReceive(cloudPublisher, perform: { (data) in
print("data ",data)
poster.postNotification(token: data, message: self.yourMessageHere)

I included the entire gist, you already have the TextField in place, you just need to add these methods to it. And now to the Cloud.swift file.

func fetchRecords(name: String) {
print("fetch ",name)
let predicate = NSPredicate(format: "name = %@", name)
let query = CKQuery(recordType: "mediator", predicate: predicate)
publicDB.perform(query,inZoneWith: CKRecordZone.default().zoneID) { [weak self] results, error in
guard let _ = self else { return }
if let error = error {
DispatchQueue.main.async {
guard let results = results else { return }
for result in results {
print("results ",result)
let rex = result.object(forKey: "senderDevice") as? String
print("rex ",rex)
DispatchQueue.main.async {
if results.count == 0 {
print("no name ",name)

This is called when you try and send a message. Note onCommit on an iphone calls when you dismiss the keyboard, but not so in an iPad. On an iPad it is called when you hit return.

Ok, one more change. You may noticed that RemoteNotification class doesn’t take any parameters. We need to fix that. Open it change the code and make this change.

...func postNotification(token:String, message:String) {let jsonObject: [String: Any] = ["aps":["sound":"bingbong.aiff","badge":1,"alert":message]]

And with this, assuming I haven’t forgotten you should be able to do some test it again, although you’ll now need two iOS devices. Put them both in and compile and run on both.

Set the first as Luke’s device, sending to Mark. The second as Mark’s device sending to Luke. Type a message into Luke’s device and if the force is with you :) it should pop up on Mark’s device. Try it the other way around.

Bon, if it is all working we haven’t forgotten anything! If not good luck with the debugging :) no no seriously we’re not done yet. We skipped the security to make the debugging easier. Now we need to implement the security.

Now I have to admit, there is/was a bug here that is still here that muddled me for a few hours. I fantastic bug that you take note if your teacher cause it great example. But before I talk about it, here is the code. The upper picker wheel looks like this.

.onTapGesture {
let success = rsa.generateKeyPair(keySize: 2048, privateTag: "ch.cqd.noob", publicTag: "ch.cqd.noob")
if success {
let publicK = rsa.getPublicKey()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let token = appDelegate.returnToken()
cloud.searchAndUpdate(name: self.users[self.selected], publicK: publicK!, device: token)
self.sender = self.users[self.selected]
messagePublisher.send(self.sender + " Logged In")
self.disableUpperWheel = true

The lower picker wheel looks like this.

.onReceive(dataPublisher) { (data) in
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let token = appDelegate.returnToken()
self.sendingTo = self.users[self.selected2]
// self.sending person selected in second PickerView
// self.sender person selected in first PickerView sending message
// token device sender [this device] is running on encypted with sending person public key
rsa.putPublicKey(publicK: data, blockSize: 2048, keySize: 2048, privateTag: "ch.cqd.noob", publicTag: "ch.cqd.noob")
let encryptedToken = rsa.encrypt(text: token)
messagePublisher.send("Sending To " + self.sendingTo)
cloud.keepRec(name: self.sender, sender: self.sendingTo, senderDevice: token, encryptedDevice: "", token: encryptedToken)
self.disableLowerWheel = true

And the message sending looks like this.

.onTapGesture { self.users[self.selected2])
}.onReceive(cloudPublisher, perform: { (data) in
let token2Send = rsa.decprypt(encrpted: data)
print("data ",data,token2Send)
poster.postNotification(token: token2Send!, message: self.yourMessageHere)

How does it work. Well as before you claim ownership to your device and then say who you want to send a message too. It looks up the person you want to send the message too and encrypts your device token in an exchange file with their public key.

On their side, you repeat the same process. They claim their device and agree they’re going to send messages to you. It encrypts their token with your public key and saves it ready for you to pickup and send back.

Now when you want to send a message, it uses your private key that it knows cause it just created the key pair to decrypt the token you encrypted with the public key on the other side that you looked up.

But STOP, and here is the subtle bug. And the sequence in which you did what I just asked you to do will show itself up or not. If it crashed for you, try it again but follow this sequence carefully.

Now backup and re-run. Stop the app and cleanup the database, remove both the meditor records.

Run the app on device A and device B. Select the user who will claim device A. Select the user who will claim device B and then select the users you want to message on device A and device B. It will work.

Can you spot the difference. Read on.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store