© 2026 Hedgehog Software, LLC

TwitterGitHubDiscord
More
CommunitiesDocsAboutTermsPrivacy
Search
Star
Setup for Free
SupabaseS
Supabase•4w ago•
13 replies
seb1

Best way to have live viewer count with realtime presence

realtime🟠Swift/Kotlin
I want to support a realtime viewer count in my swiftui application. This is working fine with small viewer counts in dev but I speculate that this will become slow if thousands of users and coming and going quite quickly.

Older versions of the API seem to have had a presence.state that I can get some quick metadata but all that looks like its been deprecated.

Is the best way to go about this to listen to the presence stream and track user objects?

Please advise. Here is my code so far:

import SwiftUI
import Supabase

struct PresenceUser: Identifiable, Codable {
    let id: String
    let name: String
}

struct GameLiveViewerCount: View {
    
    let game: Core_V1beta_Game
    
    @State var liveUsers: [String: PresenceUser] = [:]
    @State var channel: RealtimeChannelV2?
    
    var body: some View {
        HStack {
            Image(systemName: "eye").resizable().scaledToFit().frame(width: 10, height: 10)
            Text("\(liveUsers.count)").font(.caption2)
        }
        .foregroundStyle(.secondary)
        .onAppear {
            Task { await subscribe() }
        }
        .onDisappear {
            Task { await unsubscribe() }
        }
    }
    
    func subscribe() async {
        do {
            await unsubscribe()
            await ensureSession()
            
            let channelId = "game:\(game.id):viewers"
            print("channelID: \(channelId)")
            channel = supabase.channel(channelId)
            
            if let channel {
                let presenceStream = channel.presenceChange()
                
                try await channel.subscribeWithError()
                self.channel = channel
                
                // Track current user
                let uuid = UUID().uuidString
                try await channel.track(
                    PresenceUser(id: uuid, name: "Seb")
                )
                
                // Listen to presence changes
                for await presence in presenceStream {
                    let joinedUsers = try presence.decodeJoins(as: PresenceUser.self)
                    for user in joinedUsers {
                        liveUsers[user.id] = user
                    }
                    let leftUsers = try presence.decodeLeaves(as: PresenceUser.self)
                    for user in leftUsers {
                        liveUsers.removeValue(forKey: user.id)
                    }
                }
            }
            
            
        } catch {
            print("Error: \(error)")
        }
    }
    
    func unsubscribe() async {
        if let channel {
            await channel.untrack()
            await channel.unsubscribe()
        }
    }
}
import SwiftUI
import Supabase

struct PresenceUser: Identifiable, Codable {
    let id: String
    let name: String
}

struct GameLiveViewerCount: View {
    
    let game: Core_V1beta_Game
    
    @State var liveUsers: [String: PresenceUser] = [:]
    @State var channel: RealtimeChannelV2?
    
    var body: some View {
        HStack {
            Image(systemName: "eye").resizable().scaledToFit().frame(width: 10, height: 10)
            Text("\(liveUsers.count)").font(.caption2)
        }
        .foregroundStyle(.secondary)
        .onAppear {
            Task { await subscribe() }
        }
        .onDisappear {
            Task { await unsubscribe() }
        }
    }
    
    func subscribe() async {
        do {
            await unsubscribe()
            await ensureSession()
            
            let channelId = "game:\(game.id):viewers"
            print("channelID: \(channelId)")
            channel = supabase.channel(channelId)
            
            if let channel {
                let presenceStream = channel.presenceChange()
                
                try await channel.subscribeWithError()
                self.channel = channel
                
                // Track current user
                let uuid = UUID().uuidString
                try await channel.track(
                    PresenceUser(id: uuid, name: "Seb")
                )
                
                // Listen to presence changes
                for await presence in presenceStream {
                    let joinedUsers = try presence.decodeJoins(as: PresenceUser.self)
                    for user in joinedUsers {
                        liveUsers[user.id] = user
                    }
                    let leftUsers = try presence.decodeLeaves(as: PresenceUser.self)
                    for user in leftUsers {
                        liveUsers.removeValue(forKey: user.id)
                    }
                }
            }
            
            
        } catch {
            print("Error: \(error)")
        }
    }
    
    func unsubscribe() async {
        if let channel {
            await channel.untrack()
            await channel.unsubscribe()
        }
    }
}
Supabase banner
SupabaseJoin
Supabase gives you the tools, documentation, and community that makes managing databases, authentication, and backend infrastructure a lot less overwhelming.
45,816Members
Resources
Was this page helpful?

Similar Threads

Recent Announcements

Similar Threads

realtime presence
SupabaseSSupabase / help-and-questions
4y ago
Best way to handle realtime connections?
SupabaseSSupabase / help-and-questions
6mo ago
Realtime Presence reconnection handling
SupabaseSSupabase / help-and-questions
4mo ago
Realtime Presence Docs conflict with type system
SupabaseSSupabase / help-and-questions
8mo ago