Using iOS Notifications, Cryptography and iCloud to build your own Chat App II
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 II, you need to go back to part I first, indeed you need to look at the notifications series before do that ideally.
Ok, so in part I we covered the overall plan. The cryptographic code and the cloud databases we’re going to be using. In this article I we’re going to make a stab at the UI [Using SwiftUI and Combine] and do the cloudKit code.
Lets start with some SwiftUI. I want to try and keep simple as possible cause I am a neophyte when it comes to SwiftUI and I already blinded you with science with the cyptographic code.
I decided to have two picker views and a single text field as my interface. You select the person you want to be and you select the ID of the person you want to talk to.
Lets populate our new database then with a few names. Open your browser window to the CloudKit Dashboard, choose the data tab and add a few directory records to the public database. I’v gone with biblical theme and added Matthew, Mark, Luke and John to mine.

Make sure you add them to the Public Database, it might still work if you add them to the private one; but only if your two devices are logged into the same iCloud Account, which one assumes they won’t be.
Ok, before we do the interface we got a little more structure to build out under it. Go back to your project and create a Cloud.swift class, within this we’re going to flesh out our CloudKit methods.
import Foundation
import CloudKit
import Combinelet pingPublisher = PassthroughSubject<String, Never>()class Cloud: NSObject {var publicDB:CKDatabase!override init() {
super.init()
publicDB = CKContainer.default().publicCloudDatabase
}
func getDirectory() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "directory", 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 {
print("error",error)
}
return
}
guard let results = results else { return }
for result in results {
print("results ",result)
let name = result.object(forKey: "name") as? StringDispatchQueue.main.async {
pingPublisher.send(name!)
}
}
}
}
In this code we define references to the public databases and initialise it and then define a method to query our directory. Within it was also define a combine protocol object which we send an update to when we get it, in this case the names we find on the database. Our contentView class in the meantime will be watching out for that pingPublisher and react accordingly.
Ok, some Swift UI Code and a kludge. Yes, sorry a kludge which isn’t a new SwiftUI nomenclature, it is a compromise. I want to keep things simple, so I am going to use a fixed size array, and this rule that says I can only deal with eight names. We’ll revisit in a later article. I include everything here cause it makes it far easier to copy and paste.
//
// ContentView.swift
// noob
//
// Created by localadmin on 14.02.20.
// Copyright © 2020 Mark Lucking. All rights reserved.
//import SwiftUI
import AVFoundation
import Combine// Methods from previous projectlet notify = LocalNotifications()
let poster = RemoteNotifications()// Methods from pervious articlelet rsa = RSA()
let cloud = Cloud()// new Codestruct ContentView: View {
@State var yourMessageHere = ""// The kludge, This is very limited cause we need to reserve space in advance, should use binding
@State var users = ["","","","","","","",""]
@State var selected2 = 0
@State var selected = 0
@State var output:String = "" {
didSet {
print("You send \(output)")
}
}
@State var index = 0var body: some View {
VStack {
Text("noobChat").onAppear() {
cloud.getDirectory()
}
Picker(selection: $selected, label: Text("Address")) {
ForEach(0 ..< users.count) {
Text(self.users[$0])
}
}.pickerStyle(WheelPickerStyle())
.padding().onReceive(pingPublisher) { (data) in
self.users[self.index] = data
if self.index < self.users.count - 1 {
self.index = self.index + 1
} else {
self.index = 0
}
}.onTapGesture {
debugPrint(self.users[self.selected])
}
TextField("Message", text: $yourMessageHere, onCommit: {
self.output = self.yourMessageHere
})
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Picker(selection: $selected2, label: Text("Addresse")) {
ForEach(0 ..< users.count) {
Text(self.users[$0])
}
}.pickerStyle(WheelPickerStyle())
.padding()
.onTapGesture {
debugPrint(self.users[self.selected])
}
}
}
}struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}extension Binding {
func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
return Binding(
get: { self.wrappedValue },
set: { selection in
self.wrappedValue = selection
handler(selection)
})
}
}
At the top we created objects of the methods for local and remote notifications, we don’t need local; but I left it in for future developments. Next we defined objects for RSA and CloudKit code.
Next we defined two PickerViews with a message field in between them. The top one you notice has a reference to the pingPublisher we defined in the Cloud.swift code.
Bon, if you’ve been following all along you should be in a position to run it again. Swipe on the top PickerView and a few seconds later it’ll populate both with the names in your directory. We’re almost done, read on.