I recently updated our CloudKit collaboration invite codebase to use the new UIActivityController
and NSItemProvider
invitation as described in Apple's documentation. We previously used UICloudSharingController
's init(preparationHandler:)
, which is since deprecated.
We have all of the previous functionality in place: we successfully create a CKShare, send the invite out, engage the share, and collaborate. However, we cannot get the Messages CKShare
preview to use our custom image and title (henceforth referred to as “collaboration metadata”). Previously, while using UICloudSharingController
's init(preparationHandler:)
to commence the share invite, the collaboration metadata successfully displayed in the Messages conversation. Now, we have a generic icon of our app and “Shared with App-Name" title, leading to a loss of contextual integrity for the invite flow.
My question: How do we make the collaboration metadata appear in the Messages conversation?
Here is our code for creating the UIActivityController
, NSItemProvider
, CKShare
, and other related entities. It encapsulates the entire CloudKit CKShare
invite setup. You will note that we do configure the CKShare
with metadata, and we do set the LPLinkMetadata
on the UIActivityItemsConfiguration
. GitHub Gist.
The metadata does successfully appear in the UIActivityController
and the CKShare
's image and title are available to the person receiving the share once they engage it and open it in our app – but the Messages preview item retains the generic message content. Also please note that this issue does occur in the production environment.
As a final note, examining UICloudSharingController
's definition leads me to believe that supplying a UIActivityItemSource
is the key to getting correct Messages collaboration metadata in place. My efforts at using an item adhering to UIActivityItemSource
in the UIActivityViewController
used to send the share did not yield the rich previews and displayed metadata I am aiming for.
The preview of a share (CKShare
) shown in iMessage does display the value of CKShare.SystemFieldKey.title
for me. I confirmed that by using the Sharing Core Data objects between iCloud users sample in the following way:
a. Download the Apple sample mentioned above. Follow the Readme to run it on your iOS devices and confirm that the sample works on your side by adding and sharing a photo.
b. Find PhotoContextMenu.swift
in the project and change it in the following way:
- Add the UI in
menuButtons()
to trigger the test code. The code I added is wrapped with// TEST:
and// TEST: End.
:
private func menuButtons() -> some View {
...
if PersistenceController.shared.privatePersistentStore.contains(manageObject: photo) {
#if os(watchOS)
Button(action: {
createNewShare(photo: photo)
}) {
MenuButtonLabel(title: "New Share", systemImage: "square.and.arrow.up")
}
.disabled(isPhotoShared)
// TEST: Using UIActivityViewController to create a new share and obseve the rich preview in iMessage.
#elseif os(iOS)
Button(action: {
createNewShareWithActivityViewController(photo: photo)
}) {
MenuButtonLabel(title: "New Share", systemImage: "square.and.arrow.up")
}
// TEST: End.
#else
ShareLink(item: photo, preview: SharePreview("A cool photo to share!")) {
MenuButtonLabel(title: "New Share", systemImage: "square.and.arrow.up")
}
.disabled(isPhotoShared)
#endif
...
}
- Add the following extension to provide the function that creates a share with
UIActivityViewController
.
// TEST: Using UIActivityViewController to create a new share and obseve the rich preview in iMessage.
#if os(iOS)
import UIKit
extension PhotoContextMenu {
private func createNewShareWithActivityViewController(photo: Photo) {
toggleProgress.toggle()
Task { @MainActor in
let itemProvider = NSItemProvider()
itemProvider.registerCKShare(container: PersistenceController.shared.cloudKitContainer,
allowedSharingOptions: .standard, preparationHandler: {
let persistentContainer = PersistenceController.shared.persistentContainer
let (_, share, _) = try await persistentContainer.share([photo], to: nil)
share[CKShare.SystemFieldKey.title] = "<Your custom share title>"
return share
})
if let presentingViewController = rootViewController {
let activityViewController = UIActivityViewController(activityItems: [itemProvider], applicationActivities: nil)
presentingViewController.present(activityViewController, animated: true)
}
}
}
private var rootViewController: UIViewController? {
for scene in UIApplication.shared.connectedScenes {
if scene.activationState == .foregroundActive,
let sceneDeleate = (scene as? UIWindowScene)?.delegate as? UIWindowSceneDelegate,
let window = sceneDeleate.window {
return window?.rootViewController
}
}
print("\(#function): Failed to retrieve the window's root view controller.")
return nil
}
}
#endif
// TEST: End.
c. Build and run the app, and try to add a new photo and share it again.
At step c, I see that the value of share[CKShare.SystemFieldKey.title]
is on the preview, as shown in the attached screenshot.
You might try the same flow to check if you see the same result.
Best,
——
Ziqiao Chen
Worldwide Developer Relations.