commit 8d6a819380ddc268c111ae085f31bd6c0a8ed878
parent 9420b8be60df96208558b6973e97274780937844
Author: Michael Camilleri <[email protected]>
Date: Tue, 28 Apr 2026 11:25:55 +0900
Use existing link in share sheet
If a user has already shared a game via iCloud, Crossmate should
recognise that and reuse the existing URL. This commit makes changes to
accomplish that.
Co-Authored-By: Co-Authored-By: Codex GPT 5.5 <[email protected]>
Diffstat:
2 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/Crossmate/Sync/ShareController.swift b/Crossmate/Sync/ShareController.swift
@@ -68,6 +68,37 @@ final class ShareController {
}
}
+ /// Returns the saved public share URL for a game, if Crossmate already
+ /// knows about its `CKShare`. Stale local share references are cleared so
+ /// the caller can safely offer to create a fresh link.
+ func existingShareLink(for gameID: UUID) async throws -> URL? {
+ let ctx = persistence.viewContext
+ let request = NSFetchRequest<GameEntity>(entityName: "GameEntity")
+ request.predicate = NSPredicate(format: "id == %@", gameID as CVarArg)
+ request.fetchLimit = 1
+ guard let entity = try ctx.fetch(request).first else {
+ throw ShareError.gameNotFound
+ }
+ guard entity.databaseScope == 0 else {
+ throw ShareError.notAnOwner
+ }
+ guard let existingName = entity.ckShareRecordName else {
+ return nil
+ }
+
+ do {
+ let share = try await fetchExistingShare(
+ recordName: existingName,
+ zoneName: entity.ckZoneName ?? "game-\(gameID.uuidString)"
+ )
+ return share.url
+ } catch let error as CKError where error.code == .unknownItem {
+ entity.ckShareRecordName = nil
+ try ctx.save()
+ return nil
+ }
+ }
+
private func prepareShareRecord(
for gameID: UUID,
publicPermission: CKShare.ParticipantPermission
diff --git a/Crossmate/Views/GameShareItem.swift b/Crossmate/Views/GameShareItem.swift
@@ -9,7 +9,9 @@ struct GameShareSheet: View {
@Environment(\.dismiss) private var dismiss
@State private var shareURL: URL?
@State private var errorMessage: String?
+ @State private var isLoadingExistingLink = false
@State private var isCreating = false
+ @State private var didLoadExistingLink = false
@State private var didCopy = false
var body: some View {
@@ -59,6 +61,12 @@ struct GameShareSheet: View {
ShareLink(item: shareURL) {
Label("Send Link", systemImage: "square.and.arrow.up")
}
+ } else if isLoadingExistingLink {
+ HStack {
+ Label("Checking Link", systemImage: "link")
+ Spacer()
+ ProgressView()
+ }
} else {
Button {
Task { await createLink() }
@@ -71,7 +79,7 @@ struct GameShareSheet: View {
}
}
}
- .disabled(isCreating)
+ .disabled(isCreating || isLoadingExistingLink)
}
}
@@ -96,11 +104,28 @@ struct GameShareSheet: View {
Button("Done") { dismiss() }
}
}
+ .task {
+ await loadExistingLink()
+ }
+ }
+ }
+
+ private func loadExistingLink() async {
+ guard !didLoadExistingLink else { return }
+ didLoadExistingLink = true
+ isLoadingExistingLink = true
+ errorMessage = nil
+ defer { isLoadingExistingLink = false }
+
+ do {
+ shareURL = try await shareController.existingShareLink(for: gameID)
+ } catch {
+ errorMessage = describe(error)
}
}
private func createLink() async {
- guard !isCreating else { return }
+ guard !isCreating, !isLoadingExistingLink else { return }
isCreating = true
didCopy = false
errorMessage = nil