SkillAgentSearch skills...

DSFQuickActionBar

A spotlight-inspired quick action bar for macOS. AppKit/SwiftUI

Install / Use

/learn @dagronf/DSFQuickActionBar
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

DSFQuickActionBar

A spotlight-inspired quick action bar for macOS.

<p align="center"> <img src="https://img.shields.io/github/v/tag/dagronf/DSFQuickActionBar" /> <img src="https://img.shields.io/badge/macOS-10.13+-red" /> <img src="https://img.shields.io/badge/Swift-5.4-orange.svg" /> <img src="https://img.shields.io/badge/SwiftUI-2.0+-green" /> <a href="https://swift.org/package-manager"> <img src="https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat" alt="Swift Package Manager" /></a> <img src="https://img.shields.io/badge/License-MIT-lightgrey" /> </p> <p align="center"> <a href="https://github.com/dagronf/dagronf.github.io/blob/master/art/projects/DSFQuickActionBar/qab_search.png?raw=true"> <img src="https://github.com/dagronf/dagronf.github.io/blob/master/art/projects/DSFQuickActionBar/qab_search.png?raw=true" alt="Swift Package Manager" width="250"/></a> </a> <a href="https://github.com/dagronf/dagronf.github.io/blob/master/art/projects/DSFQuickActionBar/qab_results.png?raw=true"> <img src="https://github.com/dagronf/dagronf.github.io/blob/master/art/projects/DSFQuickActionBar/qab_results.png?raw=true" alt="Swift Package Manager" width="250"/></a> </a> <a href="./Art/kbd-shortcuts.png"> <img src="./Art/kbd-shortcuts.png" alt="Swift Package Manager" width="250"/></a> </a> </p>

Why?

I've seen this in other mac applications (particularly Spotlight and Boop) and it's very useful and convenient.

Features

  • macOS AppKit Swift Support
  • macOS AppKit SwiftUI Support
  • Completely keyboard navigable
  • Optional keyboard shortcuts
  • Asynchronous API to avoid beachballing on complex queries.

You can present a quick action bar in the context of a window (where it will be centered above and within the bounds of the window as is shown in the image above) or centered in the current screen (like Spotlight currently does).

Demos

You can find macOS demo apps in the Demos subfolder.

  • Simple Demo - a simple AppKit application demonstrating a synchronous quick action bar using AppKit, SwiftUI and custom cell types
  • Doco Demo - AppKit demo used for generating images for the website
  • Faux Spotlight - An AppKit demo showing asynchronous searching support using MDItemQuery()
  • SwiftUI Demo - A SwiftUI demonstration
  • StatusBar Item Demo - Demonstrates displaying a quick action bar from a statusbar item (in the menu).

Process

  1. Present the quick action bar, automatically focussing on the edit field so your hands can stay on the keyboard
  2. User starts typing in the search field
  3. For each change to the search term -
    1. The contentSource will be asked for the item(s) that 'match' the search term (itemsForSearchTerm). The items request is asynchronous, and can be completed at any point in the future (as long as it hasn't been cancelled by another search request)
    2. For each item, the contentSource will be asked to provide a view which will appear in the result table for that item (viewForItem)
    3. When the user either double-clicks on, or presses the return key on a selected item row, the contentSource will be provided with the item (didActivateItem)
  4. The quick action bar will automatically dismiss if
    1. The user clicks outside the quick action bar (ie. it loses focus)
    2. The user presses the escape key
    3. The user double-clicks an item in the result table
    4. The user selects a row and presses 'return'

Implementing for AppKit

You present a quick action bar by :-

  1. creating an instance of DSFQuickActionBar
  2. set the content source on the instance
  3. call the present method.

Presenting

Call the present method on the quick action bar instance.

| Name | Type | Description | |-----------------------|------------|-------------| | parentWindow | NSWindow | The window to present the quick action bar over, or nil to display for the current screen (ala Finder Spotlight) | | placeholderText | String | The placeholder text to display in the edit field | | searchImage | NSImage | The image to display on the left of the search edit field. If nil, uses the default magnifying glass image | | initialSearchText | String | Provide an initial search string to appear when the bar displays | | width | CGFloat | Force the width of the action bar | | showKeyboardShortcuts | Bool | Display keyboard shortcuts (↩︎, ⌘1 -> ⌘9) for the first 10 selectable items | | didClose | callback | Called when the quick action bar closes |

Content Source

The contentSource (DSFQuickActionBarContentSource) provides the content and feedback for the quick action bar. The basic mechanism is similar to NSTableViewDataSource/NSTableViewDelegate in that the control will :-

  1. query the contentSource for items matching a search term (itemsForSearchTerm)
  2. ask the contentSource for a view for each displayed item (viewForItem)
  3. indicate that the user has pressed/clicked a selection in the results.
  4. (optional) indicate to the contentSource that the quick action bar has been dismissed.

Delegate style content source

itemsForSearchTermTask

func quickActionBar(_ quickActionBar: DSFQuickActionBar, itemsForSearchTermTask task: DSFQuickActionBar.SearchTask)

Called when the control needs a array of items to display within the control that match a search term. The definition of 'match' is entirely up to you - you can perform any check you want.

The task object contains the search term and a completion block to call when the search results become available. If the search text changes during an asynchronous search call the task is marked as invalid and the result will be ignored.

Simple synchronous example

If you have code using the old synchronous API, it's relatively straightforward to convert your existing code to the new api.

func quickActionBar(_ quickActionBar: DSFQuickActionBar, itemsForSearchTermTask task: DSFQuickActionBar.SearchTask)
   let results = countryNames.filter { $0.name.startsWith(task.searchTerm) }
   task.complete(with: results)
}
Simple asynchronous example
var currentSearch: SomeRemoteSearchMechanism?
func quickActionBar(_ quickActionBar: DSFQuickActionBar, itemsForSearchTermTask task: DSFQuickActionBar.SearchTask)
   currentSearch?.cancel()
   currentSearch = SomeRemoteSearchMechanism(task.searchTerm) { [weak self] results in
      task.complete(with: results)
      self?.currentSearch = nil
   }
}

viewForItem

func quickActionBar(_ quickActionBar: DSFQuickActionBar, viewForItem item: AnyHashable, searchTerm: String) -> NSView?

Return the view to be displayed in the row for the item. The search term is also provided to allow the view to be customized for the search term (eg. highlighting the match in the name)


canSelectItem

func quickActionBar(_ quickActionBar: DSFQuickActionBar, canSelectItem item: AnyHashable) -> Bool

Called when a item will be selected (eg. by keyboard navigation or clicking). Return false if this row should not be selected (eg. it's a separator)


didSelectItem

func quickActionBar(_ quickActionBar: DSFQuickActionBar, didSelectItem item: AnyHashable)

Called when an item is selected within the list.


didActivateItem

// Swift
func quickActionBar(_ quickActionBar: DSFQuickActionBar, didActivateItem item: AnyHashable)

Indicates the user activated an item in the result list. The 'item' parameter is the item that was selected by the user


didCancel

func quickActionBarDidCancel(_ quickActionBar: DSFQuickActionBar)

Called if the user cancels the quick action bar (eg. by hitting the esc key or clicking outside the bar)


<details> <summary>Swift Example</summary>

Swift Example

A simple AppKit example using Core Image Filters as the contentSource.

class ViewController: NSViewController {
   let quickActionBar = DSFQuickActionBar()
   override func viewDidLoad() {
      super.viewDidLoad()

      // Set the content source for the quick action bar
      quickActionBar.contentSource = self
   }

   @IBAction func selectFilter(_ sender: Any) {
      // Present the quick action bar
      quickActionBar.present(placeholderText: "Search for filters…")
   }
}

// ContentSource delegate calls
extension ViewController: DSFQuickActionBarContentSource {
   func quickActionBar(_ quickActionBar: DSFQuickActionBar, itemsForSearchTerm searchTerm: String) -> [AnyHashable] {
      return Filter.search(searchTerm)
   }

   func quickActionBar(_ quickActionBar: DSFQuickActionBar, viewForItem item: AnyHashable, searchTerm: String) -> NSView? {
      guard let filter = item as? Filter else { fatalError() }
      // For the demo, just return a simple text field with the filter's name
      return NSTextField(labelWithString: filter.userPresenting)
   }

   func quickActionBar(_ quickActionBar: DSFQuickActionBar, didActivateItem item: AnyHashable) {
      Swift.print("Activated item \(item as? Filter)")
   }
   
   func quickActionBarDidCancel(_ quickActionBar: DSFQuickActionBar) {
      Swift.print("Cancelled!")
   }
}

// the datasource for the Quick action bar. Each filter represents a CIFilter
struct Filter: Hashable, CustomStringConvertible {
   let name: String // The name is unique within our dataset, thus the default equality will be enough to uniquely identify
   var userPresenting: String { return CIFilter.localizedName(forFilterName: self.name) ?? self.name }
   var description: String { name }

   // All of the available filters
   static var AllFilters: [Filter] = {
      let filterNames = CIFilter.filterNames(inCategory: nil).sorted()
      return filterNames.map { name in Filter(name: name) }
   }()

   //
View on GitHub
GitHub Stars149
CategoryDevelopment
Updated17d ago
Forks18

Languages

Swift

Security Score

100/100

Audited on Mar 17, 2026

No findings