This post.demystifies a powerful yet often underused feature in the Apple ecosystem. Many developers find iCloud integration—whether through CloudKit, key-value storage, or iCloud Drive—intimidating due to scattered documentation and complex setup. By offering a clear, beginner-friendly guide with a working example, you not only fill a common knowledge gap but also empower others to build more seamless, cloud-synced experiences across devices. It’s a great way to share practical knowledge, boost your credibility, and contribute to best practices in the iOS dev community.

iCloud

iCloud is Apple’s cloud-based storage and computing service that allows users to securely store data such as documents, photos, music, app data, and backups across all their Apple devices. It provides a seamless way to keep content in sync, making it accessible from iPhones, iPads, Macs, and even Windows PCs. With services like iCloud Drive, iCloud Photos, and iCloud Backup, users benefit from automatic data management and recovery options, which enhances their overall experience with Apple’s ecosystem.

For app developers, integrating iCloud offers a range of benefits that can significantly improve user engagement and satisfaction. By using iCloud technologies such as CloudKit, developers can enable real-time data synchronization and seamless transitions between devices. For instance, a note taken on an iPhone can instantly appear on a Mac or iPad without requiring manual uploads or additional login steps. This functionality not only enhances user convenience but also opens doors for multi-device collaboration and continuity in usage.

Moreover, iCloud integration can simplify backend infrastructure for developers. With CloudKit, developers don’t need to manage their own servers for syncing user data — Apple handles the storage, security, and data transfer. This reduces development time and operational overhead, while still providing users with fast, secure, and reliable cloud features. It also adds credibility to the app by aligning it with Apple’s high standards for privacy and performance, making iCloud integration a smart and strategic choice for apps within the Apple ecosystem.

Setup iCloud on simulator

For start working we need to fulfill 2 basic requirement: First one is having an iOS Development (or Enterprise) account for having access to iCloud console and second in iOS Simulator (or real device) be sure that you have sign in your Apple development account:

Simulator Screenshot - iPhone 16 Pro - 2025-04-24 at 10.30.52

Last but not least, be sure that iCloud Drive, Sync this iPhone switch is on:

Simulator Screenshot - iPhone 16 Pro - 2025-04-24 at 10.31.29

iOS Ranking app

The app we are going to implement to demonstrate iCloud usage will allow users to enter their name and a point value. This app will be distributed across multiple user devices, enabling each user to submit their name and score. It will also display a global ranking based on the collected data.

Once we have created our blank iOS project, on target signing & capabilities add iCloud:

Add a new container:

Type its container name, has to be prefixed by iCloud. :

Ready:

To update your app when changes occur in iCloud, you need to handle silent push notifications. By default, enabling the iCloud capability also includes Push Notifications. However, Background Modes are not enabled automatically—so be sure to add the Background Modes capability and check the “Remote notifications” option.

For source code app we are going to focus only in CloudkitManager, view is very simple and doest apport too much. Nevertheless you will find code respository GitHub link at the end of the post:

import CloudKit
import Foundation


class RankingViewModel: ObservableObject {
    @Published var scores: [PlayerScore] = []
    private var database = CKContainer(identifier: "iCloud.jca.iCloudRanking").publicCloudDatabase
    
    init() {
        fetchScores()
        setupSubscription()

        NotificationCenter.default.addObserver(
            forName: .cloudKitUpdate,
            object: nil,
            queue: .main
        ) { _ in
            self.fetchScores()
        }
    }

    func fetchScores() {
        let query = CKQuery(recordType: "Score", predicate: NSPredicate(value: true))
        let sort = NSSortDescriptor(key: "points", ascending: false)
        query.sortDescriptors = [sort]

        database.perform(query, inZoneWith: nil) { records, error in
            DispatchQueue.main.async {
                if let records = records {
                    self.scores = records.map { PlayerScore(record: $0) }.sorted { $0.points > $1.points }
                    print("Fetching successfull")
                } else if let error = error {
                    print("Error fetching scores: \(error.localizedDescription)")
                }
            }
        }
    }

    func addScore(name: String, points: Int) {
        let record = CKRecord(recordType: "Score")
        record["name"] = name as CKRecordValue
        record["points"] = points as CKRecordValue

        database.save(record) { _, error in
            if let error = error {
                print("Error saving score: \(error.localizedDescription)")
            } else {
                print("Saving successfull")
                DispatchQueue.main.async { [weak self] in
                    self?.localAddScore(record: record)
                }
            }
        }
    }
    
    private func localAddScore(record: CKRecord) {
        
        scores.append(PlayerScore(record: record))
        scores = scores.sorted { $0.points > $1.points }
    }
    
    func setupSubscription() {
        let subscriptionID = "ranking-changes"

        let predicate = NSPredicate(value: true)
        let subscription = CKQuerySubscription(
            recordType: "Score",
            predicate: predicate,
            subscriptionID: subscriptionID,
            options: [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion]
        )

        let notificationInfo = CKSubscription.NotificationInfo()
        notificationInfo.shouldSendContentAvailable = true  // Silent
        subscription.notificationInfo = notificationInfo

        database.save(subscription) { returnedSub, error in
            if let error = error {
                print("❌ Subscription error: \(error.localizedDescription)")
            } else {
                print("✅ Subscription saved!")
            }
        }
    }
}

This Swift code defines a RankingViewModel class that interfaces with Apple’s CloudKit to manage a leaderboard-style ranking system. It fetches, updates, and stores player scores in an iCloud public database (iCloud.jca.iCloudRanking) using CloudKit. When the class is initialized, it automatically retrieves existing scores from CloudKit and sets up a subscription to receive real-time updates when scores are added, modified, or deleted. It also listens for a custom cloudKitUpdate notification and refetches scores when triggered. All fetched scores are stored in the @Published array scores, allowing SwiftUI views observing this view model to update dynamically.

The fetchScores() function queries the CloudKit database for records of type “Score”, sorting them by the number of points in descending order. These records are converted into PlayerScore instances (assumed to be a custom data model) and stored in the scores array. The addScore() function allows new scores to be submitted to the database. Once saved, the new score is locally appended and sorted in the scores array via localAddScore(). Additionally, the setupSubscription() method ensures the app receives silent push notifications when there are any changes to the “Score” records in CloudKit, keeping the leaderboard data synchronized across devices.

When we deploy:

Screenshot

Issue, new ranking is not updated and we can read on Console log:

For fixing that we have to make a few adjustments on iCloud Console.

iCloud Console

For having access to iCloud Console, just type ‘https://icloud.developer.apple.com/dashboard’ on your favourite browser and login with your Apple Developer (or Enterprise) account. Later on select the the iCloud containter that the app is being used:

First step is creating a Record Type for taking a look at the recently uploaded user data:

Next step is adding record fields (name and points):

For being able to retrieve data from console we have to create a Querable Index on recordName field from Score Record Type:

Now is time for checking previous stored data:

For retrieve data from device, we have to create a Sortable Index for points filed in Score Record Type:

When we deploy iOS app on a real device:

screenshot

Finally...

For final validation of the iOS app concept, I deployed the app on two different physical devices. As demonstrated in the video, when a ranking is submitted on one device, the ranking list is updated almost instantly on the other device.

Conclusions

From a programming point of view, working with iCloud is relatively straightforward. What I’ve found a bit cumbersome, however, is setting up the iCloud Console. Overall, though, using iCloud is a good idea if you need to share data across all instances of your app.

You can find source code used for writing this post in following repository

References

  • iCloud

    Apple Developer Documentation

Copyright © 2024-2025 JaviOS. All rights reserved