Skip to content

Instantly share code, notes, and snippets.

@dannyow
Created January 12, 2023 16:31
Show Gist options
  • Save dannyow/8154a6e2fc91e35948c046ff97158d57 to your computer and use it in GitHub Desktop.
Save dannyow/8154a6e2fc91e35948c046ff97158d57 to your computer and use it in GitHub Desktop.
Add overlay to the macOS app in runtime
//
// NSApp+AppIconOverlay.swift
//
// Created by Daniel on 12/01/2023.
//
import AppKit
extension NSApplication {
/// Checks if the app is launched from Xcode with attached debugger.
///
/// [Found on StackOverflow.](https://stackoverflow.com/questions/27252898/ios-detect-if-app-is-running-from-xcode)
var isDebuggerAttached: Bool {
// Buffer for "sysctl(...)" call's result.
var info = kinfo_proc()
// Counts buffer's size in bytes (like C/C++'s `sizeof`).
var size = MemoryLayout.stride(ofValue: info)
// Tells we want info about own process.
var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
// Call the API (and assert success).
let junk = sysctl(&mib, UInt32(mib.count), &info, &size, nil, 0)
assert(junk == 0, "sysctl failed")
// Finally, checks if debugger's flag is present yet.
return (info.kp_proc.p_flag & P_TRACED) != 0
}
///
/// Adds an overlay to the app'a icon with a given text and optional background color.
/// Uses `NSColor.controlAccentColor` as a default background color.
///
/// Usage example:
/// ```
/// func applicationDidFinishLaunching(_: Notification) {
/// ...
/// if NSApp.isDebuggerAttached {
/// NSApp.addAppIconOverlay(with: "debug")
/// }
/// ...
/// }
/// ```
func addAppIconOverlay(with text: String, background: NSColor = NSColor.controlAccentColor) {
if let image = applicationIconImage {
self.applicationIconImage = NSImage(size: image.size, flipped: false) { rect in
let shadow = NSShadow()
shadow.shadowColor = NSColor.black
shadow.shadowOffset = NSSize(width: 0, height: 0)
shadow.shadowBlurRadius = 2
let attributes: [NSAttributedString.Key: Any] = [
.shadow: shadow,
.font: NSFont.boldSystemFont(ofSize: 20.0),
.foregroundColor: NSColor.white,
]
let textSize = text.size(withAttributes: attributes)
// Make the backdrop behind the text a bit taller to have a space for the shadow
let heightOffset = textSize.height * 0.2
let height = textSize.height + heightOffset
let textCoords = CGPoint(x: (image.size.width - textSize.width) / 2, y: heightOffset / 2.0)
// Draw the orignal app's icon
image.draw(in: rect)
// Draw the background for text
let rect = NSRect(x: 0, y: 0, width: image.size.width, height: textSize.height + heightOffset)
let path = NSBezierPath(roundedRect: rect, xRadius: height / 2, yRadius: height / 2)
background.withAlphaComponent(0.75).setFill()
path.fill()
// Draw the text
text.draw(at: textCoords, withAttributes: attributes)
return true
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment