🎯 Recommended Samples
Balanced sample collections from various categories for you to explore
macOS Desktop Features Swift Samples
macOS Swift desktop-specific examples including file dialogs, message boxes, and system tray
💻 Message Boxes swift
🟢 simple
⭐⭐
Display alert dialogs, confirmation dialogs, and custom message boxes using NSAlert
⏱️ 25 min
🏷️ swift, macos, desktop, gui
Prerequisites:
Basic Swift, Cocoa/AppKit
// macOS Swift Message Boxes Examples
// Using NSAlert for various dialog types
import Cocoa
import Foundation
// 1. Simple Alert Dialog
class SimpleAlertDialog {
func showAlert(title: String, message: String) {
print("\n--- Simple Alert ---")
print("Title: \(title)")
print("Message: \(message)")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
let response = alert.runModal()
print("Response: \(response.rawValue)")
}
func showError(title: String, message: String) {
print("\n--- Error Alert ---")
print("Title: \(title)")
print("Message: \(message)")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .critical
alert.addButton(withTitle: "OK")
alert.runModal()
}
func showWarning(title: String, message: String) {
print("\n--- Warning Alert ---")
print("Title: \(title)")
print("Message: \(message)")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .warning
alert.addButton(withTitle: "OK")
alert.runModal()
}
}
// 2. Confirmation Dialog
class ConfirmationDialog {
enum Response {
case yes
case no
case cancelled
init(_ modalResponse: NSApplication.ModalResponse) {
switch modalResponse {
case .alertFirstButtonReturn:
self = .yes
case .alertSecondButtonReturn:
self = .no
default:
self = .cancelled
}
}
}
func confirm(title: String, message: String) -> Response {
print("\n--- Confirmation Dialog ---")
print("Question: \(message)")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
alert.addButton(withTitle: "Yes")
alert.addButton(withTitle: "No")
let response = Response(alert.runModal())
print("User response: \(response)")
return response
}
func confirmDestructive(title: String, message: String, item: String) -> Response {
print("\n--- Destructive Confirmation ---")
print("Warning: This action cannot be undone")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message + "\n\nAre you sure you want to delete '\(item)'?"
alert.alertStyle = .critical
alert.addButton(withTitle: "Delete")
alert.addButton(withTitle: "Cancel")
let response = Response(alert.runModal())
return response
}
func confirmWithThirdOption(title: String, message: String) -> Int {
print("\n--- Three-Option Confirmation ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
alert.addButton(withTitle: "Yes")
alert.addButton(withTitle: "No")
alert.addButton(withTitle: "Cancel")
let response = alert.runModal()
print("Response button: \(response.rawValue - NSApplication.ModalResponse.alertFirstButtonReturn.rawValue)")
return response.rawValue - NSApplication.ModalResponse.alertFirstButtonReturn.rawValue
}
}
// 3. Custom Alert with Icon
class CustomIconAlert {
func showWithIcon(title: String, message: String, icon: NSImage?) {
print("\n--- Custom Icon Alert ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
if let icon = icon {
alert.icon = icon
print("Custom icon set: \(icon.size)")
}
alert.addButton(withTitle: "OK")
alert.runModal()
}
func showWithSystemIcon(title: String, message: String, iconName: String) {
print("\n--- System Icon Alert ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
if let icon = NSImage(systemSymbolName: iconName, accessibilityDescription: nil) {
alert.icon = icon
print("Using system icon: \(iconName)")
}
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.runModal()
}
}
// 4. Input Dialog
class InputDialog {
func showTextInput(title: String, message: String, defaultValue: String = "") -> String? {
print("\n--- Text Input Dialog ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
input.stringValue = defaultValue
alert.accessoryView = input
alert.window.initialFirstResponder = input
let response = alert.runModal()
if response == .alertFirstButtonReturn {
let value = input.stringValue
print("Input: \(value)")
return value
}
print("Input cancelled")
return nil
}
func showPasswordInput(title: String, message: String) -> String? {
print("\n--- Password Input Dialog ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
let input = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
alert.accessoryView = input
alert.window.initialFirstResponder = input
let response = alert.runModal()
if response == .alertFirstButtonReturn {
let password = input.stringValue
print("Password received: \(password.count) characters")
return password
}
return nil
}
func showMultiLineInput(title: String, message: String, defaultValue: String = "") -> String? {
print("\n--- Multi-line Input Dialog ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
let scrollView = NSScrollView(frame: NSRect(x: 0, y: 0, width: 400, height: 100))
let textView = NSTextView()
textView.textContainer?.containerSize = NSSize(width: 400, height: 100)
textView.textContainer?.widthTracksTextView = true
textView.isHorizontallyResizable = false
textView.isVerticallyResizable = true
textView.autoresizingMask = .height
textView.string = defaultValue
scrollView.documentView = textView
scrollView.hasVerticalScroller = true
alert.accessoryView = scrollView
alert.window.initialFirstResponder = textView
let response = alert.runModal()
if response == .alertFirstButtonReturn {
let value = textView.string
print("Multi-line input: \(value.count) characters")
return value
}
return nil
}
}
// 5. Selection Dialog
class SelectionDialog {
func showChoice(title: String, message: String, options: [String]) -> Int? {
print("\n--- Selection Dialog ---")
print("Options: \(options)")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
for option in options {
alert.addButton(withTitle: option)
}
let response = alert.runModal()
let selectedIndex = Int(response.rawValue - NSApplication.ModalResponse.alertFirstButtonReturn.rawValue)
if selectedIndex >= 0 && selectedIndex < options.count {
print("Selected: \(options[selectedIndex]) (index: \(selectedIndex))")
return selectedIndex
}
return nil
}
func showRadioGroup(title: String, message: String, options: [(label: String, tag: Int)]) -> Int? {
print("\n--- Radio Group Dialog ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
let radioGroup = NSStackView()
radioGroup.orientation = .vertical
radioGroup.spacing = 8
var buttons: [NSButton] = []
for (index, option) in options.enumerated() {
let button = NSButton(radioButtonWithTitle: option.label, target: nil, action: nil)
button.tag = option.tag
if index == 0 { button.state = .on }
buttons.append(button)
radioGroup.addArrangedSubview(button)
}
alert.accessoryView = radioGroup
let response = alert.runModal()
if response == .alertFirstButtonReturn,
let selected = buttons.first(where: { $0.state == .on }) {
print("Selected: \(selected.title) (tag: \(selected.tag))")
return selected.tag
}
return nil
}
}
// 6. Progress Dialog
class ProgressDialog {
func showProgress(title: String, message: String, maxValue: Double = 100) {
print("\n--- Progress Dialog ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
let progress = NSProgressIndicator(frame: NSRect(x: 0, y: 0, width: 300, height: 20))
progress.minValue = 0
progress.maxValue = maxValue
progress.isIndeterminate = false
alert.accessoryView = progress
progress.startAnimation(nil)
// Show as sheet or non-modal
alert.window.level = .floating
alert.runModal()
progress.stopAnimation(nil)
}
func showIndeterminateProgress(title: String, message: String) {
print("\n--- Indeterminate Progress Dialog ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
let progress = NSProgressIndicator(frame: NSRect(x: 0, y: 0, width: 300, height: 20))
progress.style = .spinning
progress.isIndeterminate = true
alert.accessoryView = progress
progress.startAnimation(nil)
alert.runModal()
progress.stopAnimation(nil)
}
}
// 7. Sheet Dialog (Attached to Window)
class SheetDialog {
func showSheet(window: NSWindow, title: String, message: String) {
print("\n--- Sheet Dialog ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.beginSheetModal(for: window) { response in
print("Sheet closed with response: \(response.rawValue)")
}
}
func showSheetConfirm(window: NSWindow, title: String, message: String, completion: @escaping (Bool) -> Void) {
print("\n--- Sheet Confirmation ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .warning
alert.addButton(withTitle: "Yes")
alert.addButton(withTitle: "No")
alert.beginSheetModal(for: window) { response in
let confirmed = response == .alertFirstButtonReturn
print("Sheet confirm: \(confirmed)")
completion(confirmed)
}
}
}
// 8. Toast Notification
class ToastNotification {
func show(message: String, duration: TimeInterval = 3) {
print("\n--- Toast Notification ---")
print("Message: \(message)")
let alert = NSAlert()
alert.messageText = ""
alert.informativeText = message
alert.alertStyle = .informational
alert.showsSuppressionButton = true
alert.suppressionButton.title = ""
// Auto-dismiss after duration
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
alert.window.close()
}
alert.runModal()
}
func showInWindow(window: NSWindow, message: String, position: NSRect) {
print("\n--- Toast in Window ---")
let toast = NSPanel(
contentRect: NSRect(x: 0, y: 0, width: 300, height: 50),
styleMask: [.borderless],
backing: .buffered,
defer: false
)
let label = NSTextField(labelWithString: message)
label.frame = NSRect(x: 10, y: 10, width: 280, height: 30)
label.alignment = .center
toast.contentView?.addSubview(label)
toast.level = .floating
toast.alphaValue = 0.9
window.addChildWindow(toast, ordered: .above)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
window.removeChildWindow(toast)
toast.close()
}
}
}
// 9. Login Dialog
class LoginDialog {
func showLogin() -> (username: String, password: String)? {
print("\n--- Login Dialog ---")
let alert = NSAlert()
alert.messageText = "Login"
alert.informativeText = "Enter your credentials"
alert.alertStyle = .informational
alert.addButton(withTitle: "Login")
alert.addButton(withTitle: "Cancel")
let view = NSView(frame: NSRect(x: 0, y: 0, width: 250, height: 70))
let usernameLabel = NSTextField(labelWithString: "Username:")
usernameLabel.frame = NSRect(x: 0, y: 45, width: 80, height: 20)
view.addSubview(usernameLabel)
let usernameField = NSTextField(frame: NSRect(x: 90, y: 45, width: 150, height: 20))
view.addSubview(usernameField)
let passwordLabel = NSTextField(labelWithString: "Password:")
passwordLabel.frame = NSRect(x: 0, y: 15, width: 80, height: 20)
view.addSubview(passwordLabel)
let passwordField = NSSecureTextField(frame: NSRect(x: 90, y: 15, width: 150, height: 20))
view.addSubview(passwordField)
alert.accessoryView = view
alert.window.initialFirstResponder = usernameField
let response = alert.runModal()
if response == .alertFirstButtonReturn {
let username = usernameField.stringValue
let password = passwordField.stringValue
print("Login attempt for: \(username)")
return (username, password)
}
return nil
}
}
// 10. Complex Dialog with Multiple Controls
class ComplexDialog {
func showPreferences() -> [String: Any]? {
print("\n--- Complex Preferences Dialog ---")
let alert = NSAlert()
alert.messageText = "Preferences"
alert.alertStyle = .informational
alert.addButton(withTitle: "Save")
alert.addButton(withTitle: "Cancel")
let view = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 180))
// Checkbox 1
let checkbox1 = NSButton(checkboxWithTitle: "Enable notifications", target: nil, action: nil)
checkbox1.state = .on
checkbox1.frame = NSRect(x: 10, y: 150, width: 280, height: 20)
view.addSubview(checkbox1)
// Checkbox 2
let checkbox2 = NSButton(checkboxWithTitle: "Auto-save documents", target: nil, action: nil)
checkbox2.state = .on
checkbox2.frame = NSRect(x: 10, y: 120, width: 280, height: 20)
view.addSubview(checkbox2)
// Popup button
let popupLabel = NSTextField(labelWithString: "Theme:")
popupLabel.frame = NSRect(x: 10, y: 95, width: 80, height: 17)
view.addSubview(popupLabel)
let popup = NSPopUpButton(frame: NSRect(x: 100, y: 90, width: 190, height: 25))
popup.addItem(withTitle: "Light")
popup.addItem(withTitle: "Dark")
popup.addItem(withTitle: "Auto")
view.addSubview(popup)
// Slider
let sliderLabel = NSTextField(labelWithString: "Font size:")
sliderLabel.frame = NSRect(x: 10, y: 65, width: 80, height: 17)
view.addSubview(sliderLabel)
let slider = NSSlider(frame: NSRect(x: 100, y: 60, width: 190, height: 20))
slider.minValue = 10
slider.maxValue = 24
slider.integerValue = 13
view.addSubview(slider)
// Text field
let textFieldLabel = NSTextField(labelWithString: "Name:")
textFieldLabel.frame = NSRect(x: 10, y: 35, width: 80, height: 17)
view.addSubview(textFieldLabel)
let nameField = NSTextField(frame: NSRect(x: 100, y: 32, width: 190, height: 22))
view.addSubview(nameField)
alert.accessoryView = view
let response = alert.runModal()
if response == .alertFirstButtonReturn {
let preferences: [String: Any] = [
"notifications": checkbox1.state == .on,
"autoSave": checkbox2.state == .on,
"theme": popup.titleOfSelectedItem ?? "",
"fontSize": slider.integerValue,
"name": nameField.stringValue
]
print("Preferences saved: \(preferences)")
return preferences
}
return nil
}
}
// 11. Alert with Checkbox
class CheckboxAlert {
func showAlertWithCheckbox(title: String, message: String, checkboxText: String, completion: @escaping (Bool, Bool) -> Void) {
print("\n--- Alert with Checkbox ---")
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.showsSuppressionButton = true
alert.suppressionButton.title = checkboxText
let response = alert.runModal()
let checked = alert.suppressionButton.state == .on
let okPressed = response == .alertFirstButtonReturn
print("OK pressed: \(okPressed), Checkbox: \(checked)")
completion(okPressed, checked)
}
func showDontAskAgain(title: String, message: String) {
showAlertWithCheckbox(title: title, message: message, checkboxText: "Don't ask again") { _, dontAsk in
if dontAsk {
print("User chose not to be asked again")
// Save preference to UserDefaults
UserDefaults.standard.set(true, forKey: "dontAskAgain")
}
}
}
}
// 12. Modal Window Dialog
class ModalWindowDialog {
func showModalWindow(title: String, content: String) {
print("\n--- Modal Window Dialog ---")
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 400, height: 300),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
window.title = title
let contentView = NSView(frame: NSRect(x: 0, y: 0, width: 400, height: 300))
let textField = NSTextField(labelWithString: content)
textField.frame = NSRect(x: 20, y: 150, width: 360, height: 100)
textField.alignment = .center
contentView.addSubview(textField)
let closeButton = NSButton(frame: NSRect(x: 150, y: 20, width: 100, height: 30))
closeButton.title = "Close"
closeButton.target = window
closeButton.action = #selector(NSWindow.close)
contentView.addSubview(closeButton)
window.contentView = contentView
window.isReleasedWhenClosed = false
let app = NSApplication.shared
app.runModal(for: window)
window.close()
}
}
// Main demonstration
func demonstrateMessageBoxes() {
print("=== macOS Swift Message Boxes Examples ===")
// Note: These dialogs require GUI context
print("\nNote: Message boxes require NSApplication context")
print("The following examples show the implementation pattern")
print("\n--- Example Alert Types ---")
let simpleAlert = SimpleAlertDialog()
print("Simple alert: title='Information', message='This is a message'")
let confirmation = ConfirmationDialog()
print("Confirmation: title='Confirm', message='Are you sure?'")
let inputDialog = InputDialog()
print("Text input: title='Enter Name', message='Please enter your name:'")
let loginDialog = LoginDialog()
print("Login dialog: requesting username and password")
let complex = ComplexDialog()
print("Complex dialog: multiple controls (checkboxes, popup, slider, text field)")
print("\n=== All Message Box Examples Completed ===")
}
// Run demonstration
demonstrateMessageBoxes()
💻 File Dialogs swift
🟡 intermediate
⭐⭐⭐
Open and save file dialogs with filters and custom options using NSOpenPanel and NSSavePanel
⏱️ 30 min
🏷️ swift, macos, desktop, gui
Prerequisites:
Intermediate Swift, Cocoa/AppKit
// macOS Swift File Dialogs Examples
// Using NSOpenPanel and NSSavePanel for file selection
import Cocoa
import Foundation
// 1. Basic Open File Dialog
class BasicFileDialog {
@discardableResult
func openFile() -> String? {
print("\n--- Basic Open File Dialog ---")
let panel = NSOpenPanel()
panel.title = "Select a file"
panel.showsResizeIndicator = true
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.allowsMultipleSelection = false
let response = panel.runModal()
if response == .OK {
if let url = panel.url {
print("Selected file: \(url.path)")
return url.path
}
}
print("No file selected")
return nil
}
}
// 2. Open File with Filters
class FileDialogWithFilters {
enum FileFilter {
case images
case text
case pdf
case all
case custom(types: [String])
var extensions: [String] {
switch self {
case .images:
return ["jpg", "jpeg", "png", "gif", "bmp", "tiff"]
case .text:
return ["txt", "md", "rtf"]
case .pdf:
return ["pdf"]
case .all:
return ["*"]
case .custom(let types):
return types
}
}
var description: String {
switch self {
case .images:
return "Image Files"
case .text:
return "Text Files"
case .pdf:
return "PDF Documents"
case .all:
return "All Files"
case .custom(let types):
return types.joined(separator: ", ")
}
}
}
func openFile(allowedTypes: [FileFilter]) -> [URL]? {
print("\n--- Open File with Filters ---")
let panel = NSOpenPanel()
panel.title = "Select a file"
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.allowsMultipleSelection = true
// Set allowed file types
let allTypes = allowedTypes.flatMap { $0.extensions }
panel.allowedFileTypes = allTypes.contains("*") ? nil : allTypes
// Display filters in title
let filterDescriptions = allowedTypes.map { $0.description }.joined(separator: ", ")
print("Allowed types: \(filterDescriptions)")
let response = panel.runModal()
if response == .OK {
let urls = panel.urls
print("Selected \(urls.count) file(s):")
urls.forEach { print(" - \($0.lastPathComponent)") }
return urls
}
return nil
}
}
// 3. Save File Dialog
class SaveFileDialog {
@discardableResult
func saveFile(defaultName: String = "untitled.txt") -> URL? {
print("\n--- Save File Dialog ---")
let panel = NSSavePanel()
panel.title = "Save File"
panel.nameFieldStringValue = defaultName
panel.canCreateDirectories = true
panel.isExtensionHidden = false
let response = panel.runModal()
if response == .OK {
if let url = panel.url {
print("Save to: \(url.path)")
// Create empty file
if let content = "Sample content\n".data(using: .utf8) {
try? content.write(to: url)
print("File created successfully")
}
return url
}
}
print("Save cancelled")
return nil
}
func saveFileWithExtension(defaultName: String, allowedExtensions: [String]) -> URL? {
print("\n--- Save File with Extension Validation ---")
let panel = NSSavePanel()
panel.title = "Save File"
panel.nameFieldStringValue = defaultName
panel.allowedFileTypes = allowedExtensions
panel.allowsOtherFileTypes = false
let response = panel.runModal()
if response == .OK {
if let url = panel.url {
print("Saved to: \(url.path)")
print("Extension: \(url.pathExtension)")
return url
}
}
return nil
}
}
// 4. Open Directory Dialog
class DirectoryDialog {
func selectDirectory() -> URL? {
print("\n--- Open Directory Dialog ---")
let panel = NSOpenPanel()
panel.title = "Select a folder"
panel.canChooseFiles = false
panel.canChooseDirectories = true
panel.allowsMultipleSelection = false
let response = panel.runModal()
if response == .OK {
if let url = panel.url {
print("Selected directory: \(url.path)")
// List directory contents
if let files = try? FileManager.default.contentsOfDirectory(atPath: url.path) {
print("Contents (\(files.count) items):")
files.forEach { print(" - \($0)") }
}
return url
}
}
return nil
}
func selectMultipleDirectories() -> [URL]? {
print("\n--- Select Multiple Directories ---")
let panel = NSOpenPanel()
panel.title = "Select folders"
panel.canChooseFiles = false
panel.canChooseDirectories = true
panel.allowsMultipleSelection = true
let response = panel.runModal()
if response == .OK {
let urls = panel.urls
print("Selected \(urls.count) director(y/ies):")
urls.forEach { print(" - \($0.path)") }
return urls
}
return nil
}
}
// 5. Open Panel with Custom Options
class CustomFileDialog {
func openWithCustomOptions() -> [URL]? {
print("\n--- Open Panel with Custom Options ---")
let panel = NSOpenPanel()
panel.title = "Select files to import"
panel.prompt = "Import"
panel.message = "Choose one or more files to import into the application"
// Configure options
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.allowsMultipleSelection = true
// Set starting directory
if let downloadsURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first {
panel.directoryURL = downloadsURL
print("Starting in Downloads folder")
}
// Show hidden files
panel.showsHiddenFiles = false
let response = panel.runModal()
if response == .OK {
let urls = panel.urls
print("Selected \(urls.count) file(s) for import")
return urls
}
return nil
}
func saveWithAccessoryView() -> URL? {
print("\n--- Save Panel with Accessory View ---")
let panel = NSSavePanel()
panel.title = "Export Settings"
// Create accessory view
let accessoryView = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 50))
let checkbox = NSButton(checkboxWithTitle: "Include user preferences", target: nil, action: nil)
checkbox.state = .on
checkbox.frame = NSRect(x: 20, y: 25, width: 260, height: 20)
accessoryView.addSubview(checkbox)
let label = NSTextField(labelWithString: "Export options:")
label.frame = NSRect(x: 20, y: 5, width: 260, height: 15)
accessoryView.addSubview(label)
panel.accessoryView = accessoryView
let response = panel.runModal()
if response == .OK, let url = panel.url {
let includePrefs = checkbox.state == .on
print("Exported to: \(url.path)")
print("Include preferences: \(includePrefs)")
return url
}
return nil
}
}
// 6. Async File Dialog
class AsyncFileDialog {
func openFileAsync(completion: @escaping (URL?) -> Void) {
print("\n--- Async File Dialog ---")
DispatchQueue.main.async {
let panel = NSOpenPanel()
panel.title = "Select a file"
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.begin { response in
if response == .OK {
if let url = panel.url {
print("File selected asynchronously: \(url.path)")
completion(url)
return
}
}
print("No file selected")
completion(nil)
}
}
}
}
// 7. Recent Files Dialog
class RecentFilesDialog {
func showRecentDocuments() -> [URL]? {
print("\n--- Recent Documents ---")
let panel = NSOpenPanel()
panel.title = "Recent Documents"
// Use the shared document controller
let documentController = NSDocumentController.shared
if let recentURLs = documentController.recentDocumentURLs, !recentURLs.isEmpty {
print("Found \(recentURLs.count) recent document(s):")
recentURLs.prefix(10).forEach { url in
print(" - \(url.lastPathComponent)")
}
// Show panel in directory of most recent
if let mostRecent = recentURLs.first,
let parentURL = mostRecent.deletingLastPathComponent() as URL? {
panel.directoryURL = parentURL
}
let response = panel.runModal()
if response == .OK, let url = panel.url {
return [url]
}
} else {
print("No recent documents found")
// Show regular dialog
let response = panel.runModal()
if response == .OK, let url = panel.url {
return [url]
}
}
return nil
}
}
// 8. File Dialog with Preview
class FileDialogWithPreview {
func openWithPreview() -> URL? {
print("\n--- File Dialog with Preview ---")
let panel = NSOpenPanel()
panel.title = "Select an image"
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.allowedFileTypes = ["jpg", "jpeg", "png", "gif", "pdf"]
// Enable preview
panel.canDownloadItems = true
// Create custom preview accessory view
let previewView = NSImageView()
previewView.frame = NSRect(x: 0, y: 0, width: 200, height: 200)
previewView.imageScaling = .scaleProportionallyUpOrDown
previewView.wantsLayer = true
previewView.layer?.borderWidth = 1
previewView.layer?.borderColor = NSColor.separatorColor.cgColor
panel.accessoryView = previewView
// Note: In a real app, you would implement a delegate to update preview
// when selection changes
let response = panel.runModal()
if response == .OK, let url = panel.url {
print("Selected: \(url.lastPathComponent)")
// Load and display preview
if let image = NSImage(contentsOf: url) {
previewView.image = image
print("Image size: \(image.size)")
}
return url
}
return nil
}
}
// 9. File Dialog with Validation
class ValidatedFileDialog {
func openWithValidation(minFileSize: Int = 0, maxFileSize: Int = Int.max) -> URL? {
print("\n--- File Dialog with Validation ---")
let panel = NSOpenPanel()
panel.title = "Select a file"
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.message = "File size must be between \(minFileSize) and \(maxFileSize) bytes"
let response = panel.runModal()
if response == .OK, let url = panel.url {
// Validate file size
if let attributes = try? FileManager.default.attributesOfItem(atPath: url.path),
let fileSize = attributes[.size] as? UInt64 {
print("File: \(url.lastPathComponent)")
print("Size: \(fileSize) bytes")
if fileSize < UInt64(minFileSize) {
let alert = NSAlert()
alert.messageText = "File too small"
alert.informativeText = "Selected file is smaller than minimum required size (\(minFileSize) bytes)"
alert.alertStyle = .warning
alert.runModal()
return nil
}
if fileSize > UInt64(maxFileSize) {
let alert = NSAlert()
alert.messageText = "File too large"
alert.informativeText = "Selected file exceeds maximum allowed size (\(maxFileSize) bytes)"
alert.alertStyle = .warning
alert.runModal()
return nil
}
return url
}
}
return nil
}
}
// 10. Comprehensive File Dialog Manager
class FileDialogManager {
enum DialogMode {
case open
case save
case selectFolder
case selectMultiple
}
struct DialogConfig {
var title: String = "Select File"
var message: String? = nil
var defaultName: String = "untitled"
var allowedTypes: [String]? = nil
var allowsMultiple: Bool = false
var canSelectFolders: Bool = false
var startDirectory: URL? = nil
var prompt: String? = nil
static func openDialog(title: String = "Open",
message: String? = nil,
types: [String]? = nil,
multiple: Bool = false) -> DialogConfig {
var config = DialogConfig()
config.title = title
config.message = message
config.allowedTypes = types
config.allowsMultiple = multiple
return config
}
static func saveDialog(title: String = "Save",
defaultName: String = "untitled",
types: [String]? = nil) -> DialogConfig {
var config = DialogConfig()
config.title = title
config.defaultName = defaultName
config.allowedTypes = types
return config
}
}
func show(mode: DialogMode, config: DialogConfig) -> [URL]? {
print("\n--- File Dialog Manager ---")
print("Mode: \(mode), Title: \(config.title)")
switch mode {
case .open, .selectMultiple:
let panel = NSOpenPanel()
panel.title = config.title
panel.message = config.message
panel.canChooseFiles = !config.canSelectFolders
panel.canChooseDirectories = config.canSelectFolders
panel.allowsMultipleSelection = config.allowsMultiple
panel.allowedFileTypes = config.allowedTypes
if let prompt = config.prompt {
panel.prompt = prompt
}
panel.directoryURL = config.startDirectory
let response = panel.runModal()
if response == .OK {
return panel.urls.isEmpty ? nil : panel.urls
}
case .save:
let panel = NSSavePanel()
panel.title = config.title
panel.message = config.message
panel.nameFieldStringValue = config.defaultName
panel.allowedFileTypes = config.allowedTypes
panel.directoryURL = config.startDirectory
if let prompt = config.prompt {
panel.prompt = prompt
}
let response = panel.runModal()
if response == .OK, let url = panel.url {
return [url]
}
case .selectFolder:
let panel = NSOpenPanel()
panel.title = config.title
panel.canChooseFiles = false
panel.canChooseDirectories = true
panel.allowsMultipleSelection = config.allowsMultiple
panel.directoryURL = config.startDirectory
let response = panel.runModal()
if response == .OK {
return panel.urls.isEmpty ? nil : panel.urls
}
}
return nil
}
}
// Main demonstration
func demonstrateFileDialogs() {
print("=== macOS Swift File Dialogs Examples ===")
let manager = FileDialogManager()
// Note: These dialogs require GUI context
print("\nNote: Dialogs require NSApplication context")
print("The following examples show the implementation pattern")
// Example configurations
print("\n--- Example Configurations ---")
let openConfig = FileDialogManager.DialogConfig.openDialog(
title: "Open Image",
message: "Select an image file",
types: ["jpg", "jpeg", "png", "gif"],
multiple: true
)
print("Open config: \(openConfig.title), types: \(openConfig.allowedTypes ?? [])")
let saveConfig = FileDialogManager.DialogConfig.saveDialog(
title: "Save Document",
defaultName: "document.txt",
types: ["txt", "md"]
)
print("Save config: \(saveConfig.title), default: \(saveConfig.defaultName)")
print("\n=== All File Dialog Examples Completed ===")
}
// Run demonstration
demonstrateFileDialogs()
💻 System Tray swift
🟡 intermediate
⭐⭐⭐
Create and manage system tray menu bar application with NSStatusItem
⏱️ 35 min
🏷️ swift, macos, desktop, gui
Prerequisites:
Intermediate Swift, Cocoa/AppKit
// macOS Swift System Tray Examples
// Creating and managing menu bar applications with NSStatusItem
import Cocoa
import Foundation
// 1. Basic Status Item
class BasicStatusItem {
private var statusItem: NSStatusItem?
func createStatusItem() {
print("\n--- Basic Status Item ---")
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem?.button {
button.title = "🔔"
print("Status item created with icon")
}
}
func removeStatusItem() {
print("\n--- Removing Status Item ---")
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
self.statusItem = nil
print("Status item removed")
}
}
}
// 2. Status Item with Menu
class StatusItemWithMenu {
private var statusItem: NSStatusItem?
func createWithMenu() {
print("\n--- Status Item with Menu ---")
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem?.button {
button.title = "⚙️"
}
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "About", action: #selector(showAbout), keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Preferences", action: #selector(showPreferences), keyEquivalent: ","))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quitApplication), keyEquivalent: "q"))
statusItem?.menu = menu
print("Menu created with 4 items")
}
@objc private func showAbout() {
print("Menu action: About")
showAlert(message: "About this application")
}
@objc private func showPreferences() {
print("Menu action: Preferences")
showAlert(message: "Open preferences")
}
@objc private func quitApplication() {
print("Menu action: Quit")
NSApplication.shared.terminate(nil)
}
private func showAlert(message: String) {
let alert = NSAlert()
alert.messageText = message
alert.alertStyle = .informational
alert.runModal()
}
func cleanup() {
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
}
}
}
// 3. Status Item with Custom View
class StatusItemWithCustomView {
private var statusItem: NSStatusItem?
private var clickCount = 0
func createWithCustomView() {
print("\n--- Status Item with Custom View ---")
statusItem = NSStatusBar.system.statusItem(withLength: 60)
guard let button = statusItem?.button else {
print("Failed to get status item button")
return
}
button.title = ""
button.action = #selector(statusItemClicked)
button.target = self
// Create custom view
let customView = NSView(frame: NSRect(x: 0, y: 0, width: 60, height: 20))
customView.wantsLayer = true
customView.layer?.backgroundColor = NSColor.controlBackgroundColor.cgColor
customView.layer?.cornerRadius = 5
let label = NSTextField(labelWithString: "Menu")
label.frame = NSRect(x: 10, y: 2, width: 40, height: 16)
label.alignment = .center
customView.addSubview(label)
button.addSubview(customView)
print("Custom view status item created")
}
@objc private func statusItemClicked() {
clickCount += 1
print("Status item clicked (count: \(clickCount))")
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Clicked \(clickCount) times", action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Reset", action: #selector(resetCounter), keyEquivalent: ""))
statusItem?.menu = menu
statusItem?.button?.performClick(nil)
}
@objc private func resetCounter() {
clickCount = 0
print("Counter reset")
}
func cleanup() {
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
}
}
}
// 4. Dynamic Menu
class DynamicStatusMenu {
private var statusItem: NSStatusItem?
func createDynamicMenu() {
print("\n--- Dynamic Status Menu ---")
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem?.button {
button.title = "📊"
}
refreshMenu()
}
func refreshMenu() {
let menu = NSMenu()
// Dynamic timestamp
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .medium
let timeString = dateFormatter.string(from: Date())
menu.addItem(NSMenuItem(title: "Time: \(timeString)", action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
// Dynamic system info
let memoryUsage = getMemoryUsage()
menu.addItem(NSMenuItem(title: "Memory: \(memoryUsage)", action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Refresh", action: #selector(refreshClicked), keyEquivalent: "r"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quitApplication), keyEquivalent: "q"))
statusItem?.menu = menu
}
private func getMemoryUsage() -> String {
let task = ProcessInfo.processInfo
let used = UInt64(task.physicalMemory)
let formatted = ByteCountFormatter.string(fromByteCount: Int64(used), countStyle: .memory)
return formatted
}
@objc private func refreshClicked() {
print("Refreshing menu...")
refreshMenu()
}
@objc private func quitApplication() {
NSApplication.shared.terminate(nil)
}
func cleanup() {
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
}
}
}
// 5. Status Item with Toggle
class ToggleStatusItem {
private var statusItem: NSStatusItem?
private var isEnabled = false
func createToggleItem() {
print("\n--- Toggle Status Item ---")
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
updateIcon()
let menu = NSMenu()
let toggleItem = NSMenuItem(
title: "Enable",
action: #selector(toggleEnabled),
keyEquivalent: ""
)
toggleItem.tag = 1
menu.addItem(toggleItem)
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quitApplication), keyEquivalent: "q"))
statusItem?.menu = menu
}
@objc private func toggleEnabled() {
isEnabled.toggle()
updateIcon()
updateMenuItem()
print("Toggled: \(isEnabled ? "Enabled" : "Disabled")")
}
private func updateIcon() {
statusItem?.button?.title = isEnabled ? "🟢" : "🔴"
}
private func updateMenuItem() {
guard let menu = statusItem?.menu,
let item = menu.item(withTag: 1) else { return }
item.title = isEnabled ? "Disable" : "Enable"
}
@objc private func quitApplication() {
NSApplication.shared.terminate(nil)
}
func cleanup() {
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
}
}
}
// 6. Status Item with Popover
class PopoverStatusItem {
private var statusItem: NSStatusItem?
private var popover: NSPopover?
func createWithPopover() {
print("\n--- Status Item with Popover ---")
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem?.button {
button.title = "ℹ️"
button.action = #selector(togglePopover)
button.target = self
}
// Create popover
popover = NSPopover()
popover?.contentSize = NSSize(width: 300, height: 200)
popover?.behavior = .transient
popover?.contentViewController = PopoverViewController()
}
@objc private func togglePopover() {
guard let button = statusItem?.button else { return }
if let popover = popover, popover.isShown {
popover.performClose(nil)
} else {
popover?.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
}
}
func cleanup() {
popover?.close()
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
}
}
}
// Popover View Controller
class PopoverViewController: NSViewController {
override func loadView() {
view = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 200))
let label = NSTextField(labelWithString: "Popover Content")
label.frame = NSRect(x: 20, y: 150, width: 260, height: 30)
label.alignment = .center
label.font = NSFont.boldSystemFont(ofSize: 16)
view.addSubview(label)
let info = NSTextField(labelWithString: "This is a popover window.")
info.frame = NSRect(x: 20, y: 100, width: 260, height: 20)
info.alignment = .center
view.addSubview(info)
let closeButton = NSButton(frame: NSRect(x: 100, y: 20, width: 100, height: 30))
closeButton.title = "Close"
closeButton.target = self
closeButton.action = #selector(closePopover)
view.addSubview(closeButton)
}
@objc private func closePopover() {
if let popover = presentingViewController as? NSPopover {
popover.performClose(nil)
}
}
}
// 7. Status Item with Badge
class BadgedStatusItem {
private var statusItem: NSStatusItem?
private var badgeCount = 0
func createBadgedItem() {
print("\n--- Badged Status Item ---")
statusItem = NSStatusBar.system.statusItem(withLength: 40)
updateBadge()
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Increment", action: #selector(incrementBadge), keyEquivalent: "+"))
menu.addItem(NSMenuItem(title: "Decrement", action: #selector(decrementBadge), keyEquivalent: "-"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Clear", action: #selector(clearBadge), keyEquivalent: "0"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quitApplication), keyEquivalent: "q"))
statusItem?.menu = menu
}
private func updateBadge() {
guard let button = statusItem?.button else { return }
if badgeCount > 0 {
button.title = "\(badgeCount)"
// Add red circle effect
button.wantsLayer = true
button.layer?.backgroundColor = NSColor.red.cgColor
button.layer?.cornerRadius = 10
} else {
button.title = "📬"
button.layer?.backgroundColor = nil
}
print("Badge count: \(badgeCount)")
}
@objc private func incrementBadge() {
badgeCount += 1
updateBadge()
}
@objc private func decrementBadge() {
badgeCount = max(0, badgeCount - 1)
updateBadge()
}
@objc private func clearBadge() {
badgeCount = 0
updateBadge()
}
@objc private func quitApplication() {
NSApplication.shared.terminate(nil)
}
func cleanup() {
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
}
}
}
// 8. Status Item with Tooltip
class TooltipStatusItem {
private var statusItem: NSStatusItem?
func createWithTooltip() {
print("\n--- Status Item with Tooltip ---")
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem?.button {
button.title = "💡"
button.toolTip = "Click for options"
}
let menu = NSMenu()
let infoItem = NSMenuItem(title: "System Information", action: nil, keyEquivalent: "")
infoItem.toolTip = "View detailed system info"
menu.addItem(infoItem)
let prefItem = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "")
prefItem.toolTip = "Open application settings"
menu.addItem(prefItem)
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quitApplication), keyEquivalent: "q"))
statusItem?.menu = menu
print("Tooltip status item created")
}
@objc private func quitApplication() {
NSApplication.shared.terminate(nil)
}
func updateTooltip(_ tooltip: String) {
statusItem?.button?.toolTip = tooltip
print("Tooltip updated: \(tooltip)")
}
func cleanup() {
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
}
}
}
// 9. Animated Status Item
class AnimatedStatusItem {
private var statusItem: NSStatusItem?
private var frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
private var currentFrame = 0
private var timer: Timer?
private var isAnimating = false
func createAnimatedItem() {
print("\n--- Animated Status Item ---")
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem?.button {
button.title = frames[0]
}
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Start Animation", action: #selector(startAnimation), keyEquivalent: "s"))
menu.addItem(NSMenuItem(title: "Stop Animation", action: #selector(stopAnimation), keyEquivalent: "x"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quitApplication), keyEquivalent: "q"))
statusItem?.menu = menu
}
@objc private func startAnimation() {
guard !isAnimating else { return }
isAnimating = true
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.updateFrame()
}
print("Animation started")
}
@objc private func stopAnimation() {
isAnimating = false
timer?.invalidate()
timer = nil
statusItem?.button?.title = "⏸"
print("Animation stopped")
}
private func updateFrame() {
currentFrame = (currentFrame + 1) % frames.count
statusItem?.button?.title = frames[currentFrame]
}
@objc private func quitApplication() {
stopAnimation()
NSApplication.shared.terminate(nil)
}
func cleanup() {
stopAnimation()
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
}
}
}
// 10. Complete Menu Bar Application
class MenuBarApplication {
private var statusItem: NSStatusItem?
private var appState: AppState = AppState()
func create() {
print("\n--- Complete Menu Bar Application ---")
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem?.button {
button.title = "🚀"
}
buildMenu()
setupNotifications()
}
private func buildMenu() {
let menu = NSMenu()
// App section
menu.addItem(NSMenuItem(title: "About", action: #selector(showAbout), keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
// Features section
menu.addItem(NSMenuItem(title: "Connect", action: #selector(toggleConnection), keyEquivalent: "c"))
menu.addItem(NSMenuItem(title: "Sync", action: #selector(syncData), keyEquivalent: "s"))
menu.addItem(NSMenuItem.separator())
// Status section
menu.addItem(NSMenuItem(title: "Status: \(appState.status)", action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
// Preferences
menu.addItem(NSMenuItem(title: "Preferences...", action: #selector(showPreferences), keyEquivalent: ","))
// Quit
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quitApplication), keyEquivalent: "q"))
statusItem?.menu = menu
}
private func setupNotifications() {
NotificationCenter.default.addObserver(
self,
selector: #selector(statusChanged),
name: .statusDidChange,
object: nil
)
}
@objc private func showAbout() {
print("Menu: About")
showAlert(message: "Menu Bar Application v1.0")
}
@objc private func toggleConnection() {
appState.isConnected.toggle()
print("Toggled connection: \(appState.isConnected)")
updateStatusIcon()
buildMenu()
}
@objc private func syncData() {
print("Syncing data...")
showAlert(message: "Sync completed")
}
@objc private func showPreferences() {
print("Menu: Preferences")
showAlert(message: "Open preferences window")
}
@objc private func quitApplication() {
NSApplication.shared.terminate(nil)
}
@objc private func statusChanged() {
print("Status changed: \(appState.status)")
buildMenu()
}
private func updateStatusIcon() {
statusItem?.button?.title = appState.isConnected ? "🟢" : "🔴"
}
private func showAlert(message: String) {
let alert = NSAlert()
alert.messageText = message
alert.alertStyle = .informational
alert.runModal()
}
func cleanup() {
NotificationCenter.default.removeObserver(self)
if let statusItem = statusItem {
NSStatusBar.system.removeStatusItem(statusItem)
}
}
}
// Application State
class AppState {
var isConnected = false
var status: String {
return isConnected ? "Connected" : "Disconnected"
}
}
extension Notification.Name {
static let statusDidChange = Notification.Name("statusDidChange")
}
// Main demonstration
func demonstrateSystemTray() {
print("=== macOS Swift System Tray Examples ===")
// Note: System tray items require NSApplication context
print("\nNote: Status items require NSApplication context")
print("The following examples show the implementation pattern")
print("\n--- Example Configurations ---")
print("1. Basic Status Item - Simple icon in menu bar")
print("2. Status Item with Menu - Dropdown menu with actions")
print("3. Custom View - Custom view with click handling")
print("4. Dynamic Menu - Menu that updates dynamically")
print("5. Toggle Status Item - Toggle between states")
print("6. Popover - Show popover window on click")
print("7. Badge - Show notification badge count")
print("8. Tooltip - Show tooltip on hover")
print("9. Animated Item - Animated status icon")
print("10. Complete App - Full menu bar application")
print("\n=== All System Tray Examples Completed ===")
}
// Run demonstration
demonstrateSystemTray()