← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


ListlessiOS/Helpers/FPSOverlay.swift (2025B)

1import SwiftUI
2import UIKit
3
4struct FPSOverlay: View {
5    @State private var fps: Int = 0
6    @State private var targetFPS: Int = 0
7    @State private var monitor = FPSMonitor()
8
9    var body: some View {
10        Text("\(fps) / \(targetFPS) FPS")
11            .font(.system(size: 12, weight: .bold, design: .monospaced))
12            .foregroundStyle(fps >= 55 ? .green : fps >= 45 ? .yellow : .red)
13            .padding(.horizontal, 8)
14            .padding(.vertical, 4)
15            .background(.black.opacity(0.7), in: Capsule())
16            .onAppear {
17                monitor.start { actual, target in
18                    fps = actual
19                    targetFPS = target
20                }
21            }
22            .onDisappear {
23                monitor.stop()
24            }
25    }
26}
27
28@MainActor
29private class FPSMonitor {
30    private var displayLink: CADisplayLink?
31    private var lastTimestamp: CFTimeInterval = 0
32    private var frameCount: Int = 0
33    private var lastTargetFPS: Int = 0
34    private var onUpdate: ((Int, Int) -> Void)?
35
36    func start(onUpdate: @escaping (Int, Int) -> Void) {
37        self.onUpdate = onUpdate
38        let link = CADisplayLink(target: self, selector: #selector(tick))
39        link.preferredFrameRateRange = CAFrameRateRange(minimum: 80, maximum: 120, preferred: 120)
40        link.add(to: .main, forMode: .common)
41        displayLink = link
42    }
43
44    func stop() {
45        displayLink?.invalidate()
46        displayLink = nil
47    }
48
49    @objc private func tick(_ link: CADisplayLink) {
50        let interval = link.targetTimestamp - link.timestamp
51        if interval > 0 {
52            lastTargetFPS = Int(round(1.0 / interval))
53        }
54        if lastTimestamp == 0 {
55            lastTimestamp = link.timestamp
56            return
57        }
58        frameCount += 1
59        let elapsed = link.timestamp - lastTimestamp
60        if elapsed >= 1.0 {
61            onUpdate?(frameCount, lastTargetFPS)
62            frameCount = 0
63            lastTimestamp = link.timestamp
64        }
65    }
66}