Chapter 10 Notes

Notes are listed by sections.

Downloading a Video

This code block seems to call contentHandler() twice. When debugging the extension on the device I get both control print statements printed out and step by step debugging also confirms this.

    Task {
      defer {
        contentHandler(bestAttemptContent)
        print("contentHandler called inside `defer`")
      }

      do {
        let (data, response) = try await URLSession.shared.data(from: url)
        let file = response.suggestedFilename ?? url.lastPathComponent
        let destination = URL(fileURLWithPath: NSTemporaryDirectory())
          .appendingPathComponent(file)
        try data.write(to: destination)

        let attachment = try UNNotificationAttachment(identifier: "", url: destination)
        bestAttemptContent.attachments = [attachment]
        contentHandler(bestAttemptContent)
        print("contentHandler called inside Task")
      } catch {
        print("An error occurred.")
      }
    }
  }

Not sure if this is important. We just get waning that second modification is ignored like this in console: “[551306AE-7751-4565-9409-9626A9109057] Ignoring additional replacement content replies for notification request 42D6-E532”.

But using defer in this case may not be necessary. Just putting a call to contentHandler() inside catch might be enough. Or the other way around. With defer, we don’t need to call contentHandler() within the happy path of the do statement. I would opt to just put a single call to contentHandler() after the do-catch statement here.

Also in Chapter 10 there is a note:

Note: Simulator will not currently run a service extension.

As of Xcode 16.2 this is still true. It could be helpful to mention that debugging is possible on device by setting appropriate target.

BTW, I think mentioning AppConnect web Push Notifications Console for sending pushes and other interesting push capabilities could be as simple as undoubtedly great utility but without installing anything.
While web Push Notifications Console for the app is available via button right from “Signing and Capabilities” tab in Xcode project not many people seem to notice it. And for some reason many developers upload their (hopefully temporary) keys and certificates to numerous third party web services to test APN sending.

Configuring Xcode for a Service Extension”

Open the starter project for this chapter. Remember to set the team signing as discussed in Chapter 7, “Expanding the Application.”

But It seems this rather wants to point to Chapter 4: Xcode Project Setup – “Adding Capabilities” section.

Badging the App Icon | Accessing Core Data

AppGroupIdentifier is hardcoded twice as String constant.
In UserDefaults.swift

extension UserDefaults {
  // 1
  static let suiteName = "group.com.yourcompany.PushNotification"

and in Persistence.swift

  init(inMemory: Bool = false) {
    container = NSPersistentContainer(name: "Model")
    let url: URL
    if inMemory {
      url = URL(fileURLWithPath: "/dev/null")
    } else {
      let groupName = "group.com.yourcompany.PushNotification"
      url = FileManager.default
        .containerURL(forSecurityApplicationGroupIdentifier: groupName)!
        .appendingPathComponent("PushNotifications.sqlite")
    }

Perhaps this could be better done more as some more global constant so that you are sure that if you changed it in one place it takes effect all over the project.

Unfortunately we are not able to get app groups at runtime. But here is a way that will almost always work without requiring you to change hardcoded strings.

extension Bundle {
  static let applicationGroupIdentifier: String? = {
    guard let bundleId = Bundle.main.bundleIdentifier else { return nil }
    return "group.\(bundleId)"
  }()
}

Usually your app group name follows a predictable pattern, so this code can be a part of boilerplate and it will work in most projects giving you ability to stick to Bundle.applicationGroupIdentifier whenever you need to reference the app group.

You may make any modification to the payload you want — except for one. You may not remove the alert text. If you don’t have alert text, then iOS will ignore your modifications and proceed with the original payload.

This paragraph and chapter in whole might make think that there is no possibility to silence alert push notifications. While that is indeed enforced by Apple’s API, there is one exception for E2EE VoIP calls.
For such cases a developer has to apply for special entitlement: com.apple.developer.usernotifications.filtering entitlement to the Notification Service Extension target.
From Apple doc “Sending End-to-End Encrypted VoIP Calls”:

On the receiver’s device, the notification service extension processes the incoming notification and decrypts it. If it’s an incoming VoIP call, the extension calls reportNewIncomingVoIPPushPayload(_:completion:) to initiate the call. It then silences the push notification (see com.apple.developer.usernotifications.filtering).

Hello!
Thanks for the info I will try to figure it out for more.

Best Regards,
John
My Acuvue Rewards