Skip to content

Instantly share code, notes, and snippets.

@longseespace
Created May 26, 2025 14:18
Show Gist options
  • Save longseespace/8cf3a728635f6194ed024c653ce9ea38 to your computer and use it in GitHub Desktop.
Save longseespace/8cf3a728635f6194ed024c653ce9ea38 to your computer and use it in GitHub Desktop.
Simple render FPS counter for SwiftUI (NOT GPU FPS)
import SwiftUI
import Combine
class FPSCounter: ObservableObject {
@Published var fps: Double = 0.0
private var timer: Timer?
private var lastUpdateTime: Date = Date()
private var frameCount: Int = 0
// How often to update the published FPS value (e.g., every 0.5 seconds)
private let fpsUpdateInterval: TimeInterval = 0.5
// How frequently the internal timer should try to fire (e.g., aiming for 120Hz)
private let internalTimerInterval: TimeInterval = 1.0 / 120.0
init() {}
func start() {
guard timer == nil else { return }
lastUpdateTime = Date()
frameCount = 0
// This timer will fire frequently to count "frames"
timer = Timer.scheduledTimer(withTimeInterval: internalTimerInterval, repeats: true) { [weak self] _ in
self?.tick()
}
// Ensure the timer runs on high-priority run loops if needed, e.g., for UI updates
// RunLoop.current.add(timer!, forMode: .common)
}
private func tick() {
frameCount += 1
let now = Date()
let elapsedTime = now.timeIntervalSince(lastUpdateTime)
if elapsedTime >= fpsUpdateInterval {
DispatchQueue.main.async {
self.fps = Double(self.frameCount) / elapsedTime
self.frameCount = 0
self.lastUpdateTime = now
}
}
}
func stop() {
timer?.invalidate()
timer = nil
// Optionally reset FPS to 0 when stopped
// DispatchQueue.main.async {
// self.fps = 0.0
// }
}
deinit {
stop()
}
}
import SwiftUI
struct FPSView: View {
@StateObject private var fpsCounter = FPSCounter()
var body: some View {
Text("FPS: \(fpsCounter.fps, specifier: "%.1f")")
.font(.caption)
.foregroundColor(.white)
.padding(4)
.background(Color.black.opacity(0.5))
.clipShape(RoundedRectangle(cornerRadius: 5))
.onAppear {
fpsCounter.start()
}
.onDisappear {
fpsCounter.stop()
}
}
}
struct FPSView_Previews: PreviewProvider {
static var previews: some View {
FPSView()
.padding()
.previewLayout(.sizeThatFits)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment