Swift snippet: Get the SF Symbols icon for the user’s Mac

April 15, 2023 1 minute read

Starting with the Mac Studio, and continuing with the 2022 and 2023 MacBook Air/Pro, Mac mini, Mac Studio, and Mac Pro, Macs no longer have identifying model names like MacBookPro16,2 or iMac14,2. Instead, they’re now something like Mac14,9. Using a simple prefix check to figure out which “class” of Mac the app is running on no longer works.

As a new solution, you can query UniformTypeIdentifiers, which holds a comprehensive taxonomy of all PowerPC, Intel, and Apple Silicon Macs released up to the point of the running macOS version. The upside is also that you can find tons of other information on the device, such as the visual design, which works great for picking the right SF Symbol.

Here is the result of the below code on my 14″ MacBook Pro with M2 Pro:

Swift Playgrounds screenshot: Device.machine returning Mac14,9, Device.symbolName returning macbook.gen2

Really unfortunately, nothing similar seems to exist on iOS. It would have been great to have this considering we now have three major hardware designs (home bar, home button, dynamic island) to worry about.

import Cocoa
import UniformTypeIdentifiers
extension UTTagClass {
static let deviceModelCode = UTTagClass(rawValue: "com.apple.device-model-code")
}
extension UTType {
static let macBook = UTType("com.apple.mac.laptop")
static let macBookWithNotch = UTType("com.apple.mac.notched-laptop")
static let macMini = UTType("com.apple.macmini")
static let macStudio = UTType("com.apple.macstudio")
static let iMac = UTType("com.apple.imac")
static let macPro = UTType("com.apple.macpro")
static let macPro2013 = UTType("com.apple.macpro-cylinder")
static let macPro2019 = UTType("com.apple.macpro-2019")
}
struct Device {
static let machine: String? = {
#if os(macOS) || targetEnvironment(macCatalyst)
let key = "hw.model"
#else
let key = "hw.machine"
#endif
var size = size_t()
sysctlbyname(key, nil, &size, nil, 0)
let value = malloc(size)
defer {
value?.deallocate()
}
sysctlbyname(key, value, &size, nil, 0)
guard let cChar = value?.bindMemory(to: CChar.self, capacity: size) else {
return nil
}
return String(cString: cChar)
}()
private static func conforms(to type: UTType?) -> Bool {
guard let type,
let machine else {
return false
}
return UTType(tag: machine, tagClass: .deviceModelCode, conformingTo: nil)?
.conforms(to: type) ?? false
}
static let symbolName: String = {
if conforms(to: .macBookWithNotch),
#available(macOS 14.0, *) {
// macbook.gen2 was added with SF Symbols 5.0 (macOS Sonoma, 2023), but MacBooks with a notch
// were released in 2021!
return "macbook.gen2"
} else if conforms(to: .macBook) {
return "laptopcomputer"
} else if conforms(to: .macMini) {
return "macmini"
} else if conforms(to: .macStudio) {
return "macstudio"
} else if conforms(to: .iMac) {
return "desktopcomputer"
} else if conforms(to: .macPro2019) {
return "macpro.gen3"
} else if conforms(to: .macPro2013) {
return "macpro.gen2"
} else if conforms(to: .macPro) {
return "macpro"
}
return "display"
}()
}
view raw Device.swift hosted with ❤ by GitHub