Created
January 12, 2023 16:31
-
-
Save dannyow/8154a6e2fc91e35948c046ff97158d57 to your computer and use it in GitHub Desktop.
Add overlay to the macOS app in runtime
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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