Skip to main content

Leaderboards

Compete across daily, weekly, monthly, and all-time periods. Submit scores, query rankings, and get sorted snapshots — all backed by SwiftData with CloudKit sync.

Periods

PrismLeaderboardPeriod
public enum PrismLeaderboardPeriod: String, Codable, Sendable, CaseIterable {
    case daily      // Reset every 24 hours
    case weekly     // Reset every 7 days
    case monthly    // Reset every calendar month
    case allTime    // Never reset
}

Submitting Scores

Submit Score
let entry = try await manager.submitScore(
    userID: "user-42",
    displayName: "Alice",
    score: 1500,
    period: .weekly
)
print(entry.rank) // 1
  • Creates a new record if none exists for the user/period pair
  • Updates the existing record if one exists
  • Returns the entry with its computed rank

Updating Scores

Update Score
let entry = try await manager.updateScore(
    userID: "user-42",
    score: 2000,
    period: .weekly
)
Throws PrismGamificationError.leaderboardEntryNotFound if no record exists for the user in that period. Use submitScore for upsert behavior.

Querying the Leaderboard

Leaderboard Snapshot
let snapshot = try await manager.leaderboard(
    period: .weekly,
    limit: 50
)

for entry in snapshot.entries {
    print("#\(entry.rank) \(entry.displayName): \(entry.score)")
}
Default limit is 100. Entries are sorted by score descending and ranked automatically.

Rank Lookup

Single User Rank
let entry = try await manager.rank(for: "user-42", period: .weekly)
print("#\(entry.rank)\(entry.score) pts")

PrismLeaderboardEntry

Sendable, Comparable (by rank ascending), and Identifiable:
PropertyTypeDescription
idStringUser identifier
displayNameStringDisplay name
scoreIntScore for the period
rankIntComputed rank (1 = first)
Sorting
let entries = snapshot.entries.sorted() // rank 1, 2, 3...

PrismLeaderboardSnapshot

PropertyTypeDescription
entries[PrismLeaderboardEntry]Ranked entries
periodPrismLeaderboardPeriodPeriod covered
generatedAtDateWhen snapshot was generated

Resetting

Reset Period
try await manager.resetLeaderboard(period: .daily)
Deletes all entries for the given period. Other periods are unaffected.

Leaderboard Events

Events
for await event in manager.events {
    if case .leaderboardUpdated(let userID, let newRank) = event {
        print("\(userID) moved to rank #\(newRank)")
    }
}
Events fire on both submitScore and updateScore.

Multi-Period Pattern

Submit to Multiple Periods
let periods: [PrismLeaderboardPeriod] = [.daily, .weekly, .monthly, .allTime]

for period in periods {
    try await manager.submitScore(
        userID: currentUser.id,
        displayName: currentUser.name,
        score: currentUser.totalScore,
        period: period
    )
}
Submit scores to all relevant periods after each scoring event. Each period maintains its own independent rankings.