I'm coding a Mac app in SwiftUI 6.0.3 and Xcode 16. My Mac is up to date with macOS Sequoia 15.3.1. I'm trying to have a menu bar item that updates at an interval with the percentage of the CPU that I am using. This code returns no errors, and as far as I can tell, should work, but I must be missing something. When I run the app, instead of giving me a percentage, it just says "Calculating..." which is the default value of the cpuUsage variable.
import SwiftUI
import Foundation
@main
struct MenuBarApp: App {
@State private var cpuUsage: String = "Calculating..."
var body: some Scene {
// Menu bar item
MenuBarExtra("icon \(cpuUsage)") {
// Option to quit app
Button("Quit") {
NSApp.terminate(nil)
}
}
}
// Starts repeating CPU monitoring function at an interval of 1 second
func startCPUMonitoring() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
if let usage = getCPUUsage() {
cpuUsage = String(format: "%.1f%%", usage)
} else {
cpuUsage = "N/A"
}
}
}
// Retrieves CPU usage as a percentage of the total
func getCPUUsage() -> Double? {
var size = mach_msg_type_number_t(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
var cpuLoad = host_cpu_load_info()
let result = withUnsafeMutablePointer(to: &cpuLoad) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
}
}
guard result == KERN_SUCCESS else {
print("Error retrieving CPU load: \(result)")
return nil
}
let user = Double(cpuLoad.cpu_ticks.0)
let system = Double(cpuLoad.cpu_ticks.1)
let idle = Double(cpuLoad.cpu_ticks.2)
let nice = Double(cpuLoad.cpu_ticks.3)
let totalTicks = user + system + idle + nice
let cpuUsage = (user + system + nice) / totalTicks * 100.0
return cpuUsage
}
}
I've asked ChatGPT and went through the Apple Developer documentation but the problem is so niche that I can't find a single relatively recent source that discusses anything remotely similar. I think it's probably an issue with what function is being called where or the order of events or something, but I can't figure it out. Please help. I'm just trying to make this work on the latest version of macOS, it doesn't matter if it works for older versions.
Assuming your calculations are correct, try this approach using a Button("Start")
to start monitoring,
and a more precise String(format: "%.6f%%", usage)
to show any differences in the value,
as shown in this example code.
Note you need to click on the "Start" button to display the results.
@main
struct MenuBarApp: App {
@State private var cpuUsage: String = "Calculating..."
var body: some Scene {
// Menu bar item
MenuBarExtra("icon \(cpuUsage)") {
Button("Start") {
startCPUMonitoring() // <--- here to start
}
Button("Quit") {
NSApp.terminate(nil)
}
}
}
// Starts repeating CPU monitoring function at an interval of 1 second
func startCPUMonitoring() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
if let usage = getCPUUsage() {
cpuUsage = String(format: "%.6f%%", usage) // <--- here
} else {
cpuUsage = "N/A"
}
}
}
// Retrieves CPU usage as a percentage of the total
func getCPUUsage() -> Double? {
var size = mach_msg_type_number_t(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
var cpuLoad = host_cpu_load_info()
let result = withUnsafeMutablePointer(to: &cpuLoad) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
}
}
guard result == KERN_SUCCESS else {
print("Error retrieving CPU load: \(result)")
return nil
}
let user = Double(cpuLoad.cpu_ticks.0)
let system = Double(cpuLoad.cpu_ticks.1)
let idle = Double(cpuLoad.cpu_ticks.2)
let nice = Double(cpuLoad.cpu_ticks.3)
let totalTicks = user + system + idle + nice
let cpuUsage = (user + system + nice) / totalTicks * 100.0
return cpuUsage
}
}
EDIT-1:
If you want to start the CPU monitoring immediately without
having to click "Start",
then try this approach using a @Observable class CPUMonitor
.
This will observe any changes in the cpuUsage
and update the View/MenuBar.
@Observable class CPUMonitor {
var cpuUsage: String = "Calculating..."
init() {
startCPUMonitoring() // <--- here
}
// Starts repeating CPU monitoring function at an interval of 1 second
func startCPUMonitoring() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
if let usage = self.getCPUUsage() {
self.cpuUsage = String(format: "%.6f%%", usage) // <--- here
} else {
self.cpuUsage = "N/A"
}
}
}
// Retrieves CPU usage as a percentage of the total
func getCPUUsage() -> Double? {
var size = mach_msg_type_number_t(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
var cpuLoad = host_cpu_load_info()
let result = withUnsafeMutablePointer(to: &cpuLoad) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
}
}
guard result == KERN_SUCCESS else {
print("Error retrieving CPU load: \(result)")
return nil
}
let user = Double(cpuLoad.cpu_ticks.0)
let system = Double(cpuLoad.cpu_ticks.1)
let idle = Double(cpuLoad.cpu_ticks.2)
let nice = Double(cpuLoad.cpu_ticks.3)
let totalTicks = user + system + idle + nice
let cpuUsage = (user + system + nice) / totalTicks * 100.0
return cpuUsage
}
}
@main
struct MenuBarApp: App {
let cpuMonitor = CPUMonitor() // <--- here
var body: some Scene {
MenuBarExtra("icon \(cpuMonitor.cpuUsage)") { // <--- here
Button("Quit") {
NSApp.terminate(nil)
}
}
}
}
Works well for me, tested on macOS 15.3.1, using Xcode 16.2.