Skip to main content

Challenges

Challenges are the core of PrismGamification. Define them as a Swift enum conforming to PrismChallenge, and the manager handles persistence, progress tracking, and events.

The PrismChallenge Protocol

Protocol Definition
public protocol PrismChallenge: RawRepresentable, CaseIterable, Hashable, Sendable
where RawValue == String {
    var title: String { get }
    var challengeDescription: String { get }
    var type: PrismChallengeType { get }
    var goal: Int { get }
    var category: String? { get }       // default: nil
    var iconName: String? { get }       // default: nil
    var points: Int { get }             // default: 0
}

Challenge Types

TypeBehaviorExample
.counterIncrements toward a goal”Complete 10 workouts”
.milestoneBinary — done or not”Create your first post”
Complete Example
enum Challenges: String, PrismChallenge, CaseIterable {
    case firstPost
    case tenLikes
    case weeklyStreak

    var title: String {
        switch self {
        case .firstPost: "First Post"
        case .tenLikes: "Ten Likes"
        case .weeklyStreak: "Weekly Streak"
        }
    }

    var challengeDescription: String {
        switch self {
        case .firstPost: "Create your very first post"
        case .tenLikes: "Receive 10 likes on your content"
        case .weeklyStreak: "Log in every day for a week"
        }
    }

    var type: PrismChallengeType {
        switch self {
        case .firstPost: .milestone
        case .tenLikes, .weeklyStreak: .counter
        }
    }

    var goal: Int {
        switch self {
        case .firstPost: 1
        case .tenLikes: 10
        case .weeklyStreak: 7
        }
    }

    var points: Int {
        switch self {
        case .firstPost: 10
        case .tenLikes: 25
        case .weeklyStreak: 50
        }
    }

    var category: String? {
        switch self {
        case .firstPost: "content"
        case .tenLikes: "social"
        case .weeklyStreak: "engagement"
        }
    }

    var iconName: String? {
        switch self {
        case .firstPost: "square.and.pencil"
        case .tenLikes: "heart.fill"
        case .weeklyStreak: "calendar"
        }
    }
}

Registration

Register all cases of your challenge enum. Idempotent — safe to call on every app launch:
Register Challenges
let manager = PrismChallengeManager(container: container)
try await manager.register(Challenges.self)

Tracking Progress

Counter Challenges

Increment
// Increment by 1
let snapshot = try await manager.increment(Challenges.tenLikes)

// Increment by N
let snapshot = try await manager.increment(Challenges.tenLikes, by: 5)
Progress auto-completes when currentValue >= goal. Values clamp to goal — never overshoot.
Calling increment on a milestone challenge throws PrismGamificationError.invalidOperation. Use complete instead.

Milestone Challenges

Complete
let snapshot = try await manager.complete(Challenges.firstPost)
Calling complete or increment on an already-completed challenge throws PrismGamificationError.challengeAlreadyCompleted.

Querying Progress

Queries
// Single challenge
let progress = try await manager.progress(for: Challenges.tenLikes)
print(progress.currentValue)  // 5
print(progress.goalValue)     // 10
print(progress.progress)      // 0.5
print(progress.isCompleted)   // false

// Check completion
let done = try await manager.isCompleted(Challenges.firstPost)

// All challenges
let all = try await manager.allProgress()

// Total points from completed challenges
let points = try await manager.totalPoints(Challenges.self)

PrismChallengeSnapshot

All queries return PrismChallengeSnapshot — a Sendable value type:
PropertyTypeDescription
challengeIDStringThe raw value of the challenge enum case
currentValueIntCurrent progress
goalValueIntTarget value
isCompletedBoolWhether completed
typeRawValueString”counter” or “milestone”
progressDouble0.0 to 1.0 (computed)
createdAtDateWhen registered
updatedAtDateLast modification
completedAtDate?When completed

Resetting

Reset
// Reset single challenge
try await manager.reset(Challenges.tenLikes)

// Reset all challenges of a type
try await manager.resetAll(Challenges.self)

Events

Subscribe to challenge events via AsyncStream:
Event Stream
for await event in manager.events {
    switch event {
    case .completed(let id, let points):
        print("\(id) completed! +\(points)")
    case .progressed(let id, let current, let goal):
        print("\(id): \(current)/\(goal)")
    default:
        break
    }
}

Error Handling

ErrorWhen
challengeNotFoundQuery/update a challenge that wasn’t registered
challengeAlreadyCompletedIncrement or complete a finished challenge
invalidOperationIncrement a milestone (use complete instead)
All errors are PrismGamificationError — equatable and localized.