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

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.

Obviously this is part VIII, you need to go back to the beginning for it to make any sense, indeed you need to look at the notifications series before do that else your struggle. Most of the parts are just 4 minutes long including code.

What we are going to do here. Well it is time to tidy things up and indeed start to polish them up. Now thinking about it, it makes no sense that you constantly log into your device, no once you downloaded it and logged in, there is not need to do so. Lets implement that with some defaults.

It also makes sense if your logged into device A, that you don’t show yourself as a possible recipient of a message. The fix for both these reasonable easy.

@State var showUpperWheel = true...Text("noobChat").onAppear() {
cloud.getDirectory()
let name = UserDefaults.standard.string(forKey: "name")
if name != nil {
self.showUpperWheel = false
self.sender = name
messagePublisher.send(self.sender + " Logged In")
}
}

We create a showUpperWheel state and set it when we login to the interface depending on if the name has been set previously.

// *** 1ST ***
self.sender = self.users[self.selected]
UserDefaults.standard.set(self.sender, forKey: "name")

We than add a line to the 1ST pickerwheel code to set the defaults when they select a user. Of course this does mean they get one chance only, but we can revisit this later.

Having stopped the pickerWheel showing after the first run, we need to move the code setting it up to the second one.

.pickerStyle(WheelPickerStyle())
.padding()
.onReceive(pingPublisher) { (data) in
if data != self.sender {
self.users[self.index] = data
if self.index < self.users.count - 1 {
self.index = self.index + 1
} else {
self.index = 0
}
}
}

It all good, you can now run it, it’ll show the pickerwheel as usual the first time and then it won’t the second.

Ok, lets take stock a moment and think about what is missing. When I started the project one of the shortcuts I took was to add two users to by icloud database via the dashboard. It’s a shortcut we need to bridge now.

So the plan is this. You start the app, and get the opportunity to add the names of your friends, so an admin screen. When you finished doing so you save them to the cloud and the app than invites you to choose which one you it should associate with the device your using. You select it and then it switches to a third view showing just the lower pickerview and the noobs fields.

To implement this I need to change two files. Obviously ContentView.swift file and secondly our cloud.swift code. Lets review what we need to add to it.

func seekAndTell(names:[String])  {
var namesCopy = names
let predicate = NSPredicate(format: "TRUEPREDICATE")
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 {
let name = result.object(forKey: "name") as? String
let finder = names.firstIndex(of: name!)
if finder != nil {
if finder! < namesCopy.count {
namesCopy[finder!] = ""
}
}
}
if namesCopy.count > 0 {
var boxes:[CKRecord] = []
for index in 0 ..< namesCopy.count {
if namesCopy[index] != "" {
let record = CKRecord(recordType: "directory")
record.setObject(namesCopy[index] as __CKRecordObjCValue, forKey: "name")
boxes.append(record)
}
}
print("merge ",boxes)
self!.saveToCloud(names: boxes)
}
}
}
func saveToCloud(names:[CKRecord]) {
let saveRecordsOperation = CKModifyRecordsOperation()
saveRecordsOperation.recordsToSave = names
saveRecordsOperation.savePolicy = .allKeys
saveRecordsOperation.modifyRecordsCompletionBlock = { savedRecords,deletedRecordID, error in
if error != nil {
print("error",error)
} else {
print("saved ",savedRecords?.count)
DispatchQueue.main.async {
turnOffAdmin.send()
messagePublisher.send("Saved Names")
}
}
}
publicDB.add(saveRecordsOperation)
}

We have two methods here. The first downloads all the records found in the directory. It then compares them too the ones in the pickerview array, and array you’ll be adding/deleting names too; and passes on the new ones to a second method that saves them. We have an assumption here that we cannot have two users with the same name.

It saves them and turns off the admin function in the interface. Now for the ContentView.swift. We need a textfield, and at least three buttons. One to add names, one to delete names and one to say we’re finished write to cloud.

let turnOffAdmin = PassthroughSubject<Void, Never>()...
@State var showAdmin = true
...if showAdmin {
Button(action: {
print("saving to icloud")
cloud.seekAndTell(names: self.users)
}) {
Image(systemName: "icloud.and.arrow.up")
}.onReceive(turnOffAdmin) { (_) in
self.showAdmin = false
self.showUpperWheel = true
}
}

Here is saving button. I added two, but you can do just one if you like. As you can see it calls the cloud method we just defined and switches the interface to the second stage. We also show a passthrough protocol that our saving cloud method will call and a State variable for turning on/off admin interface. Next the textfield + buttons.

if self.showAdmin {
HStack {
Button(action: {
let finder = self.users.firstIndex(of: self.name)
print("finder ",finder)
if finder == nil {
self.users[self.index] = self.name
if self.index < self.users.count - 1 {
self.index = self.index + 1
} else {
self.index = 0
}
}
self.name = ""
}) {
Image(systemName: "plus.circle")
}
TextField("Nobody?", text: self.$name, onEditingChanged: { (editing) in
if editing {
self.name = ""
}
}, onCommit: {
let finder = self.users.firstIndex(of: self.name)
if finder == nil {
self.users[self.index] = self.name
if self.index < self.users.count - 1 {
self.index = self.index + 1
} else {
self.index = 0
}
}
self.name = ""
})
Button(action: {
let finder = self.users.firstIndex(of: self.name)
self.users.remove(at: finder!)
self.users.append("")
self.index = self.index - 1
}) {
Image(systemName: "minus.circle")
}
}.padding()
}

As you can read we define our two buttons along with some actions linked to them and the text field in the middle for our nick names. Log into the dashboard. Delete all the record in the public and private databases for the noob app and run a test to see if we managed to get everything in there.

You should be able to add two users, than select one to be the owner, and then send a message to the other one.

Coding for 35+ years, enjoying using and learning Swift/iOS development. Writer @ Better Programming, @The StartUp, @Mac O’Clock, Level Up Coding & More

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