2.0: sync CloudKit en tiempo real vía push silencioso

NSPersistentCloudKitContainer solo sincronizaba de forma oportunista (sin
push), por lo que un plato nuevo tardaba mucho o no aparecía en el otro
dispositivo. Se activa el push de CloudKit:

- aps-environment en MealMood.entitlements vía variable APS_ENVIRONMENT
  (development en Debug, production en Release) para no romper TestFlight.
- AppDelegate con registerForRemoteNotifications() + manejo del push
  silencioso; el mirror de SwiftData importa al ser despertado.
- (Capability Push Notifications habilitada en el App ID por API.)

Pendiente: regenerar el provisioning profile AppStore con Push antes de
la build 63, y validar el sync push en iPhone+iPad vía TestFlight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
This commit is contained in:
alexandrev-tibco
2026-07-24 15:47:46 +02:00
parent 19bf3178d8
commit d957921fe3
3 changed files with 27 additions and 0 deletions
+2
View File
@@ -923,6 +923,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = MealMood/Resources/MealMood.entitlements;
APS_ENVIRONMENT = production;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 62;
DEVELOPMENT_TEAM = 2825Q76T7H;
@@ -1028,6 +1029,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = MealMood/Resources/MealMood.entitlements;
APS_ENVIRONMENT = development;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 62;
DEVELOPMENT_TEAM = 2825Q76T7H;
+23
View File
@@ -1,11 +1,32 @@
import SwiftUI
import SwiftData
import UIKit
import FirebaseCore
import FirebaseCrashlytics
#if canImport(GoogleMobileAds)
import GoogleMobileAds
#endif
/// Registers for remote notifications so CloudKit (the NSPersistentCloudKitContainer
/// behind SwiftData) receives its silent-push subscriptions and imports changes
/// in near-real-time across devices. Silent pushes are coalesced by the system,
/// so battery cost is negligible no polling.
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
application.registerForRemoteNotifications()
return true
}
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// CloudKit silent push: SwiftData's mirror imports on its own once woken;
// report new data so the system keeps delivering sync pushes.
completionHandler(.newData)
}
}
/// Whether the store is syncing through CloudKit this launch. When true, the
/// legacy iCloud KV snapshot sync must stay inert (both would fight).
enum CloudSyncRuntime {
@@ -16,6 +37,8 @@ enum CloudSyncRuntime {
@main
struct MealMoodApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
private let modelContainer: ModelContainer = {
let schema = Schema([
AppSettings.self,
+2
View File
@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>$(APS_ENVIRONMENT)</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.alexandrev.mealmood</string>