commit da4140cf18b644af269f1a71dc379317e929cc04
parent 99db6f67d273c85c0b335be5305bea5bc9d630ba
Author: Michael Camilleri <[email protected]>
Date: Mon, 27 Apr 2026 09:31:48 +0900
Revert to previous share sheet
Diffstat:
5 files changed, 23 insertions(+), 157 deletions(-)
diff --git a/Crossmate.xcodeproj/project.pbxproj b/Crossmate.xcodeproj/project.pbxproj
@@ -26,7 +26,6 @@
4A89595E3F6AB50E1D9E6BA8 /* ImportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 462CE0FD356F6137C9BFD30F /* ImportService.swift */; };
503229FF89FF7C29CEF4C16D /* Puzzle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C8064F04FC6177D987ACA2 /* Puzzle.swift */; };
54464FDFB8C71B0D3B4B61A2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FEFF257CDDD3EF0E77CBF7 /* SettingsView.swift */; };
- 69B090CECDD8360288CE10DC /* CloudSharingPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8ECFCF3E5647F6121369963 /* CloudSharingPopover.swift */; };
6BE7E91158F4DF1F71247C6D /* CellMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = B135C285570F91181595B405 /* CellMark.swift */; };
6BFB5945FCCDEC64C431C2AC /* SyncDiagnosticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C90BA83B6DC7F435A7CF24 /* SyncDiagnosticsView.swift */; };
765B50552B13175F91A25EA1 /* GridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB4BB9E160C3A59C653E7A9 /* GridView.swift */; };
@@ -155,7 +154,6 @@
DB55FC337CF72C650373210A /* PlayerColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerColor.swift; sourceTree = "<group>"; };
DB851649DE78AAAC5A928C52 /* Square.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Square.swift; sourceTree = "<group>"; };
E7AFD37B03A1C2E23E5766E6 /* PuzzleSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PuzzleSource.swift; sourceTree = "<group>"; };
- E8ECFCF3E5647F6121369963 /* CloudSharingPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudSharingPopover.swift; sourceTree = "<group>"; };
E9BD3F7EAFD344D8E10E8C3B /* ClueList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClueList.swift; sourceTree = "<group>"; };
EAC61E2582D94B1E6EC67136 /* XDFileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDFileType.swift; sourceTree = "<group>"; };
EBC4C0246B2BCE686A3516DB /* GamePlayerColorStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GamePlayerColorStoreTests.swift; sourceTree = "<group>"; };
@@ -279,7 +277,6 @@
9A4B7C6A8A23C6E4CCEC759F /* BundledBrowseView.swift */,
C0CAA5E17BD406AFEEF96196 /* CalendarDayCell.swift */,
F8E50E7BA98C88B4CAB39DC1 /* CellView.swift */,
- E8ECFCF3E5647F6121369963 /* CloudSharingPopover.swift */,
E9BD3F7EAFD344D8E10E8C3B /* ClueList.swift */,
38DDAD9D6470A894C3FD6F90 /* GameListView.swift */,
5ABB557BA10CBE9909056882 /* GameShareItem.swift */,
@@ -468,7 +465,6 @@
6BE7E91158F4DF1F71247C6D /* CellMark.swift in Sources */,
CFCA3C2C3CF6D88AE844D7AD /* CellView.swift in Sources */,
CC250D6BA9B41CB722D8A62E /* CloudService.swift in Sources */,
- 69B090CECDD8360288CE10DC /* CloudSharingPopover.swift in Sources */,
B94919176DEC6EC31637B037 /* ClueList.swift in Sources */,
DE9E4FAB098731A650F2D306 /* CrossmateApp.swift in Sources */,
C30C0C4E54E4209A22843872 /* CrossmateModel.xcdatamodeld in Sources */,
diff --git a/Crossmate/Sync/ShareController.swift b/Crossmate/Sync/ShareController.swift
@@ -1,21 +1,15 @@
import CloudKit
import CoreData
import Foundation
-import Observation
/// Manages the lifecycle of `CKShare` objects for per-game zones. Responsible
/// for creating zone-scoped shares and saving them to CloudKit, refreshing
/// existing shares on re-present, and letting participants leave a shared game.
@MainActor
-@Observable
final class ShareController {
let container: CKContainer
- @ObservationIgnored private let persistence: PersistenceController
- @ObservationIgnored private let syncEngine: SyncEngine
-
- /// Latest known iCloud account status. `nil` until the first refresh.
- /// Drives whether share UI can actually present anything.
- private(set) var accountStatus: CKAccountStatus?
+ private let persistence: PersistenceController
+ private let syncEngine: SyncEngine
enum ShareError: Error {
case gameNotFound
@@ -27,22 +21,6 @@ final class ShareController {
self.container = container
self.persistence = persistence
self.syncEngine = syncEngine
- Task { await self.refreshAccountStatus() }
- Task { await self.observeAccountChanges() }
- }
-
- func refreshAccountStatus() async {
- do {
- accountStatus = try await container.accountStatus()
- } catch {
- accountStatus = .couldNotDetermine
- }
- }
-
- private func observeAccountChanges() async {
- for await _ in NotificationCenter.default.notifications(named: .CKAccountChanged) {
- await refreshAccountStatus()
- }
}
/// Returns the `CKShare` and container for `UICloudSharingController`'s
diff --git a/Crossmate/Views/CloudSharingPopover.swift b/Crossmate/Views/CloudSharingPopover.swift
@@ -1,81 +0,0 @@
-import CloudKit
-import SwiftUI
-import UIKit
-
-/// `UIViewControllerRepresentable` over `UICloudSharingController`, intended to
-/// be the content of a SwiftUI `.popover`. Pair with
-/// `.presentationCompactAdaptation(.popover)` to keep the popover anchored to
-/// its source on iPhone instead of adapting to a bottom sheet.
-struct CloudSharingPopover: UIViewControllerRepresentable {
- let gameID: UUID
- let title: String
- let shareController: ShareController
- let onDismiss: () -> Void
-
- func makeUIViewController(context: Context) -> UICloudSharingController {
- let csc = UICloudSharingController { [shareController, gameID] (_, completion) in
- Task { @MainActor in
- do {
- let (share, container) = try await shareController.prepareShare(for: gameID)
- completion(share, container, nil)
- } catch {
- completion(nil, nil, error)
- }
- }
- }
- csc.delegate = context.coordinator
- csc.availablePermissions = [.allowReadWrite, .allowPrivate]
- return csc
- }
-
- func updateUIViewController(_ uiViewController: UICloudSharingController, context: Context) {}
-
- func makeCoordinator() -> Coordinator {
- Coordinator(
- title: title,
- gameID: gameID,
- shareController: shareController,
- onDismiss: onDismiss
- )
- }
-
- @MainActor
- final class Coordinator: NSObject, UICloudSharingControllerDelegate {
- let title: String
- let gameID: UUID
- let shareController: ShareController
- let onDismiss: () -> Void
-
- init(
- title: String,
- gameID: UUID,
- shareController: ShareController,
- onDismiss: @escaping () -> Void
- ) {
- self.title = title
- self.gameID = gameID
- self.shareController = shareController
- self.onDismiss = onDismiss
- }
-
- func itemTitle(for csc: UICloudSharingController) -> String? { title }
-
- func cloudSharingController(
- _ csc: UICloudSharingController,
- failedToSaveShareWithError error: Error
- ) {
- onDismiss()
- }
-
- func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {
- if let share = csc.share {
- try? shareController.persistShareName(share.recordID.recordName, for: gameID)
- }
- onDismiss()
- }
-
- func cloudSharingControllerDidStopSharing(_ csc: UICloudSharingController) {
- onDismiss()
- }
- }
-}
diff --git a/Crossmate/Views/GameListView.swift b/Crossmate/Views/GameListView.swift
@@ -177,9 +177,6 @@ private struct GameRowView: View {
var onResign: () -> Void = {}
var onDelete: () -> Void = {}
- @State private var showSharePopover = false
- @State private var showSignInAlert = false
-
var body: some View {
HStack(spacing: 12) {
GridThumbnailView(
@@ -209,13 +206,15 @@ private struct GameRowView: View {
}
Spacer()
if game.isOwned {
- Button {
- if shareController.accountStatus == .available {
- showSharePopover = true
- } else {
- showSignInAlert = true
- }
- } label: {
+ ShareLink(
+ item: GameShareItem(
+ gameID: game.id,
+ title: game.title,
+ container: shareController.container,
+ shareController: shareController
+ ),
+ preview: SharePreview(game.title)
+ ) {
Image(systemName: "square.and.arrow.up")
.font(.body)
.frame(width: 32, height: 32)
@@ -224,20 +223,6 @@ private struct GameRowView: View {
.buttonStyle(.borderless)
.tint(.secondary)
.compositingGroup()
- .popover(isPresented: $showSharePopover) {
- CloudSharingPopover(
- gameID: game.id,
- title: game.title,
- shareController: shareController,
- onDismiss: { showSharePopover = false }
- )
- .presentationCompactAdaptation(.popover)
- }
- .alert("Sign in to iCloud", isPresented: $showSignInAlert) {
- Button("OK", role: .cancel) {}
- } message: {
- Text("Sharing requires an iCloud account. Sign in via Settings to share games.")
- }
}
Menu {
Button { onLeave() } label: { Label("Leave", systemImage: "rectangle.portrait.and.arrow.right") }
diff --git a/Crossmate/Views/PuzzleView.swift b/Crossmate/Views/PuzzleView.swift
@@ -14,8 +14,6 @@ struct PuzzleView: View {
@State private var isConfirmingLeave = false
@State private var leaveError: String?
@State private var isRevokedBannerDismissed = false
- @State private var showSharePopover = false
- @State private var showSignInAlert = false
private func swatchImage(for color: PlayerColor) -> Image {
let tint = UIColor(color.tint)
@@ -185,34 +183,24 @@ struct PuzzleView: View {
}
.disabled(!(session.mutator.isShared && !session.mutator.isOwned) || shareController == nil)
- Button("Share Game") {
- if shareController?.accountStatus == .available {
- showSharePopover = true
- } else {
- showSignInAlert = true
+ if let shareController {
+ ShareLink(
+ item: GameShareItem(
+ gameID: session.mutator.gameID,
+ title: session.puzzle.title,
+ container: shareController.container,
+ shareController: shareController
+ ),
+ preview: SharePreview(session.puzzle.title)
+ ) {
+ Text("Share Game")
}
+ .disabled(!session.mutator.isOwned)
}
- .disabled(!session.mutator.isOwned || shareController == nil)
}
} label: {
Label("Players", systemImage: "person.2")
}
- .popover(isPresented: $showSharePopover) {
- if let shareController {
- CloudSharingPopover(
- gameID: session.mutator.gameID,
- title: session.puzzle.title,
- shareController: shareController,
- onDismiss: { showSharePopover = false }
- )
- .presentationCompactAdaptation(.popover)
- }
- }
- .alert("Sign in to iCloud", isPresented: $showSignInAlert) {
- Button("OK", role: .cancel) {}
- } message: {
- Text("Sharing requires an iCloud account. Sign in via Settings to share games.")
- }
}
}
.task {