Reputation: 3
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.
Upvotes: 0
Views: 63
Reputation: 36782
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.
Upvotes: 1