• Stars
    star
    115
  • Rank 305,916 (Top 7 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 3 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

DSFQuickActionBar

A spotlight-inspired quick action bar for macOS.

Swift Package Manager

Swift Package Manager Swift Package Manager Swift Package Manager

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)


Swift Example

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) }
   }()

   // Return filters matching the search term
   static func search(_ searchTerm: String) -> [Filter] {
      if searchTerm.isEmpty { return AllFilters }
      return Filter.AllFilters
         .filter { $0.userPresenting.localizedCaseInsensitiveContains(searchTerm) }
         .sorted(by: { a, b in a.userPresenting < b.userPresenting })
   }
}

Screenshot for the sample data

SwiftUI interface

The SwiftUI implementation is a View. You 'install' the quick action bar just like you would any other SwiftUI view. The QuickActionBar view is zero-sized, and does not display content within the view its installed on.

QuickActionBar<IdentifyingObject, IdentifyingObjectView>

The QuickActionBar template parameters represent

  • IdentifyingObject is the type of the object (eg. URL)
  • IdentifyingObjectView is the type of View used to represent IdentifyingObject in the results list (eg. Text)

You present the quick action bar by setting the visible parameter to true.

For example :-

@State var quickActionBarVisible = false
@State var selectedItem: URL = URL(...)
...
VStack {
   Button("Show Quick Action Bar") {
      quickActionBarVisible = true
   }
   QuickActionBar<URL, Text>(
      location: .window,
      visible: $quickActionBarVisible,
      selectedItem: $selectedItem,
      placeholderText: "Open Quickly",
      itemsForSearchTerm: { searchTask in
         let results = /* array of matching URLs */
         searchTask.complete(with: results)
      },
      viewForItem: { url, searchTerm in
         Text(url.path)
      }
   )
   .onChange(of: selectedItem) { newValue in
      Swift.print("Selected item \(newValue)")
   }
}
...
Parameter Description
location Where to locate the quick action bar (.window, .screen)
visible If true, presents the quick action bar on the screen
showKeyboardShortcuts Display keyboard shortcuts for the first 10 selectable items
requiredClickCount If .single, only requires the user to single-click a row to activate it (defaults to .double)
barWidth The width of the presented bar
searchTerm The search term to use, updated when the quick action bar is closed
selectedItem The item selected by the user
placeholderText The text to display in the quick action bar when the search term is empty
itemsForSearchTerm A block which returns the item(s) for the specified search term
viewForItem A block which returns the View to display for the specified item
SwiftUI Example

SwiftUI Example

A simple macOS SwiftUI example using Core Image Filters as the content.

SwiftUI View

struct DocoContentView: View {
   // Binding to update when the user selects a filter
   @State var selectedFilter: Filter?
   // Binding to show/hide the quick action bar
   @State var quickActionBarVisible = false

   var body: some View {
      VStack {
         Button("Show Quick Action Bar") {
            quickActionBarVisible = true
         }
         QuickActionBar<Filter, Text>(
            location: .screen,
            visible: $quickActionBarVisible,
            selectedItem: $selectedFilter,
            placeholderText: "Open Quickly...",
            itemsForSearchTerm: { searchTask in
               let results = filters__.search(searchTask.searchTerm)
               searchTask.complete(with: results)
            },
            viewForItem: { filter, searchTerm in
               Text(filter.userPresenting)
            }
         )
      }
   }
}

Data

/// The unique object used as the quick action bar item
struct Filter: Hashable, CustomStringConvertible {
   let name: String // The name is unique within our dataset, therefore it will be our identifier
   var userPresenting: String { return CIFilter.localizedName(forFilterName: self.name) ?? self.name }
   var description: String { name }
}

class Filters {
   // If true, displays all of the filters if the search term is empty
   var showAllIfEmpty = true

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

   // Return filters matching the search term
   func search(_ searchTerm: String) -> [Filter] {
      if searchTerm.isEmpty && showAllIfEmpty { return all }
      return all
         .filter { $0.userPresenting.localizedCaseInsensitiveContains(searchTerm) }
         .sorted(by: { a, b in a.userPresenting < b.userPresenting })
   }
}

let filters__ = Filters()

Screenshots


License

MIT. Use it and abuse it for anything you want, just attribute my work. Let me know if you do use it somewhere, I'd love to hear about it!

MIT License

Copyright © 2022 Darren Ford

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

More Repositories

1

QRCode

A quick and beautiful macOS/iOS/tvOS/watchOS QR Code generator/detector library for SwiftUI, Swift and Objective-C.
Swift
417
star
2

DSFSparkline

A lightweight sparkline component for macOS, iOS and tvOS
Swift
138
star
3

DSFDockTile

Easily display images, animations, badges and alerts to your macOS application's dock icon
Swift
104
star
4

SwiftSubtitles

A Swift package for reading/writing subtitle formats (srt, sbv, sub, vtt, csv)
Swift
69
star
5

DSFStepperView

A custom stepper text field for macOS and iOS, supporting Swift, Objective-C, SwiftUI and Catalyst
Swift
63
star
6

AppKitFocusOverlay

Add hotkey(s) to display the key focus path for any window in your AppKit application.
Swift
59
star
7

CIFilterFactory

Swift and Objective-C generated classes for built-in CIFilter types and functional interface for applying filters to an image. Type safe, auto-complete friendly and (mostly) documented.
Swift
51
star
8

DSFAppKitBuilder

A SwiftUI-style DSL for AppKit (macOS)
Swift
43
star
9

ColorPaletteCodable

A color palette/gradient reader/writer for a growing set of file formats. Swift (macOS, iOS, tvOS, watchoOS, macCatalyst, Linux)
Swift
37
star
10

Bookmark

A Swift wrapper for URL bookmarks which allow a file to be located regardless of whether it is moved or renamed.
Swift
33
star
11

MountingYard

Simple macOS app to provide quick access to mount points (eg. smb, afp, vnc etc)
Swift
32
star
12

DSFDropFilesView

A Swift NSView class for nicely supporting drag/drop files.
Swift
30
star
13

DSFColorSampler

A Swift 5 class that mimics the magnifying glass in color panel of macOSX
Swift
28
star
14

DSFFloatLabelledTextControl

A macOS Cocoa single-line NSTextField/NSSecureTextField that implements the Float Label Pattern.
Python
26
star
15

DSFToolbar

A SwiftUI-style declarative NSToolbar wrapper for macOS.
Swift
25
star
16

DFSearchKit

Swift/Objective-C framework for macOS around Apple's SearchKit
Swift
25
star
17

DSFFullTextSearchIndex

A simple Swift/Objective-C full text search (FTS) class for iOS/macOS/tvOS
Swift
25
star
18

DSFSearchField

A simple NSSearchField with a localizable, managed recent searches menu.
Swift
22
star
19

DSFInspectorPanes

Easily customisable, scrollable, collapsible inspector panels using nested NSView(s)
Swift
21
star
20

DSFComboButton

A drop-in `NSComboButton` replacement with pre macOS 13 support.
Swift
19
star
21

DSFColorPicker

A color picker for macOS
Swift
19
star
22

DSFSecureTextField

NSSecureTextField with a 'visibility' button.
Swift
19
star
23

DSFImageTools

Conveniences for handling images and colors in AppKit, UIKit, SwiftUI and CoreGraphics (Swift/Objective-C)
Swift
17
star
24

DSFLabelledTextField

A simple macOS labelled text field using Swift
Swift
16
star
25

DSFPagerControl

A simple macOS pager control
Swift
16
star
26

DSFAppearanceManager

Theme and Appearance handling for macOS Appkit (Swift/Objective-C).
Swift
15
star
27

DSFActionBar

An editable, draggable bar of buttons and menus similar to Safari's Favorites bar with overflow support for macOS (10.11 and later).
Swift
15
star
28

Bitmap

A Swift-y convenience for loading, saving and manipulating bitmap images.
Swift
15
star
29

DSFMenuBuilder

A SwiftUI-style DSL for generating `NSMenu` instances for AppKit.
Swift
14
star
30

DSFPasscodeView

A passcode entry field for macOS similar to Apple's two-factor authentication field.
Swift
14
star
31

DSFToggleButton

A macOS toggle button (inheriting from NSButton) that mimics iOS's UISwitch
Swift
14
star
32

SwiftImageReadWrite

A basic microframework of routines for doing basic importing/exporting of `CGImage` and `NSImage`/`UIImage` type images.
Swift
14
star
33

DSFBrowserView

A modern-ish NSBrowser-style control allowing complex row views (macOS, Swift). Compatible back to macOS 10.11
Swift
14
star
34

DSFFinderLabels

macOS Finder tags and labels support library for Swift and Objective-C
Swift
10
star
35

VIViewInvalidating

A swift PropertyWrapper providing automatic NSView and UIView invalidation when the properties value changes. (Backwards compatible version of @Invalidating)
Swift
10
star
36

DSFDragSlider

A macOS 2d virtual trackpad control
Swift
10
star
37

DSFImageFlipbook

A simple 'flipbook' of images that can be presented as flipbook-style animation
Swift
9
star
38

DSFLicenseKeyView

macOS License control for Objective-C/Swift
Objective-C
7
star
39

HTInfiniteScrollView

An infinitely scrollable `NSScrollView` clip view implementation
Swift
7
star
40

DSFRegex

A Swift regex class abstracting away the complexities of NSRegularExpression and Swift Strings
Swift
7
star
41

TinyCSV

A tiny Swift CSV decoder/encoder library, conforming to RFC 4180
Swift
7
star
42

DSFValueBinders

Simple Swift shared value binders
Swift
6
star
43

DSFVersion

Swift 'Numeric Status Version' class supporting major, minor, patch and build integer values and version parsing.
Swift
6
star
44

DSFTouchBar

An SwiftUI-style declarative NSTouchBar wrapper
Swift
5
star
45

DSFMaxLengthDisplayTextField

An `NSTextField` that specifies a maximum count after which text is highlighted to indicate an overflow
Swift
5
star
46

Furl

A basic wrapper around common Swift file/folder/query operations
Swift
5
star
47

libuuidpp

A simple C++ UUID class wrapping libuuid
C++
4
star
48

DSFImageDifferenceView

A NSView class to interactively display the differences between two images
Swift
4
star
49

csvlib

CSV parser for C++/Objc
C++
3
star
50

BytesParser

A simple byte-oriented parser/writer. Read and write formatted values to/from binary blobs/files with ease!
Swift
3
star
51

PPMKit

Simple read/write PPM files (P3/P6) in Swift (macOS, iOS, tvOS, watchOS, macCatalyst, Linux)
Swift
2
star
52

DSFValueStream

Generate a stream of values over time
Swift
1
star
53

DSFColorList

An iOS/macOS/tvOS/watchOS cross-platform encodable color list container
Swift
1
star
54

DSFAttributedStringBuilder

A simple Swift/Objective-C NSAttributedString builder simplifying creation of a styled NSAttributedString.
Swift
1
star
55

DSFRational

A Swift extension for floating point values to return rational components
Swift
1
star
56

DSFSimpleKeychainPassword

Simple Swift wrapper around keychain generic password handling
Swift
1
star