Notifications, the whole shebang IV using only iOS

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, principally remote ones involve Apple’s infrastructure which means you will need an Apple Developers account to use them.

Now if you just got here, you should perhaps go back to part I, part II and part III first, if not read on.

OK in part III we basically covered the installation of IBM’s SwiftJWT library and configured our JSON Web Token. The next step is to build the post itself. Add this code to your RemoteNotifications.swift file in your Noob project.

func postNotification() {...do {
let jwtString = try jwtEncoder.encodeToString(myJWT)
let content = "https://api.sandbox.push.apple.com/3/device/" + token
var loginRequest = URLRequest(url: URL(string: content)!)
loginRequest.allHTTPHeaderFields = ["apns-topic": "ch.cqd.noob",
"content-type": "application/json",
"apns-priority": "10",
"apns-push-type": "alert",
"authorization":"bearer " + jwtString]
let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main)
loginRequest.httpMethod = "POST"
let data = try? JSONSerialization.data(withJSONObject: jsonObject, options:[])
loginRequest.httpBody = data
} catch {
print("failed to encode")
}
}

Import code here, the URL for the APNs POST. Take not this is for the sandbox, you need to use a slightly different URL for production. Note too that the token mentioned here is that 64 character string we fetched when we registered our iDevice with Apple in part II. Finally note the apns-topic is the bundle identifier of your project and the jwtString the JSON Web Token we just built.

You will notice an error come up when you cut’n’paste this into your project. An error on the session line. To fix it make the RemoteNotifications a URLSessionDelegate.

class PostController: NSObject,URLSessionDelegate {

We almost finished. Now we need to move back to the ContentView.swift. The full code of which I show here.

import SwiftUIlet notify = LocalNotifications()
let poster = RemoteNotifications()
struct ContentView: View {
var body: some View {
VStack {
Button(action: {
notify.doNotification()
}) {
Text("local")
}
Button(action: {
poster.postNotification()
}) {
Text("remote")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Run the app on a real device and press the remote button, you should get a notification that will look like this.

Just in case you made mistake and you get a different status code on the console that isn’t 200. Here is table to help you figure out what is going on.

200 Success
400 Bad request
403 There was an error with the certificate or with the provider authentication token
405 The request used a bad :method value. Only POST requests are supported.
410 The device token is no longer active for the topic.
413 The notification payload was too large.
429 The server received too many requests for the same device token.
500 Internal server error
503 The server is shutting down and unavailable.

Time for play. Change the jsonObject to include a sound. Try compiling the app on a second device and run it again. Your remote notification will appear on the first only, this is that 64 character identifier in play. To get the remote notification working on the second device, you need to make a second call to the URL posting it, with a second 64 character identifier.

var jsonObject: [String: Any] = ["aps":["sound":"bingbong.aiff","badge":2,"alert":["title":"What, where, who, when, how","body":"You must be kidding"]]]

If course having read all five articles, you are surely asking yourself if there isn’t a easier way to do this. Of course there is, although not in iOS. If you just need a quick solution cause you don’t have time to do a more in depth on than you can just use a simple script like this. Obviously you’ll need to fill in the asterisks .

#!/bin/bashdeviceToken="*"authKey="../AuthKey_*.p8"
authKeyId="*"
teamId=CWGS87U262
bundleId="*"
endpoint=https://api.development.push.apple.com
read -r -d '' payload <<-'EOF'
{
"aps": {
"alert": {
"title": "my title",
"body": "my body text message"
}
}
}
}
EOF
# --------------------------------------------------------------------------base64() {
openssl base64 -e -A | tr -- '+/' '-_' | tr -d =
}
sign() {
printf "$1" | openssl dgst -binary -sha256 -sign "$authKey" | base64
}
time=$(date +%s)
header=$(printf '{ "alg": "ES256", "kid": "%s" }' "$authKeyId" | base64)
claims=$(printf '{ "iss": "%s", "iat": %d }' "$teamId" "$time" | base64)
jwt="$header.$claims.$(sign $header.$claims)"
echo $timecurl --verbose \
--header "content-type: application/json" \
--header "authorization: bearer $jwt" \
--header "apns-topic: $bundleId" \
--data "$payload" \
$endpoint/3/device/$deviceToken

Which will work, only well … you cannot publish this on the apps store.

Written by

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