一个受 Spotlight 启发的 macOS 快速操作栏。
我在其他 Mac 应用程序中见过这种操作栏(尤其是 Spotlight 和 Boop),它非常有用和方便。
您可以在窗口上下文中显示快速操作栏(它将位于窗口上方并在窗口边界内居中,如上图所示)或在当前屏幕中居中(就像 Spotlight 当前所做的那样)。
您可以在 Demos
子文件夹中找到 macOS 演示应用程序。
Simple Demo
- 一个简单的 AppKit 应用程序,演示了使用 AppKit、SwiftUI 和自定义单元格类型的同步快速操作栏Doco Demo
- 用于为网站生成图像的 AppKit 演示Faux Spotlight
- 一个 AppKit 演示,展示了使用 MDItemQuery() 的异步搜索支持SwiftUI Demo
- 一个 SwiftUI 演示StatusBar Item Demo
- 演示了从状态栏项目(在菜单中)显示快速操作栏。itemsForSearchTerm
)。 items
请求是异步的,可以在将来的任何时间点完成(只要它没有被另一个搜索请求取消)viewForItem
)didActivateItem
)您可以通过以下方式显示快速操作栏:-
DSFQuickActionBar
的实例present
方法。调用快速操作栏实例上的 present
方法。
名称 | 类型 | 描述 |
---|---|---|
parentWindow | NSWindow |
在其上显示快速操作栏的窗口,如果为 nil,则为当前屏幕显示(类似于 Finder Spotlight) |
placeholderText | String |
在编辑字段中显示的占位符文本 |
searchImage | NSImage |
显示在搜索编辑字段左侧的图像。如果为 nil,则使用默认的放大镜图像 |
initialSearchText | String |
提供在工具栏显示时出现的初始搜索字符串 |
width | CGFloat |
强制操作栏的宽度 |
showKeyboardShortcuts | Bool |
为前 10 个可选项目显示键盘快捷键(↩︎、⌘1 -> ⌘9) |
didClose | 回调 | 快速操作栏关闭时调用 |
内容源 (DSFQuickActionBarContentSource
) 提供快速操作栏的内容和反馈。基本机制类似于 NSTableViewDataSource
/NSTableViewDelegate
,控件将:-
func quickActionBar(_ quickActionBar: DSFQuickActionBar, itemsForSearchTermTask task: DSFQuickActionBar.SearchTask)
当控件需要一个条目数组以显示在控件中并与搜索词匹配时调用。 “匹配”的定义完全由您决定 - 您可以执行任何您想要的检查。
task
对象包含搜索词和一个完成块,用于在搜索结果可用时调用。 如果在异步搜索调用期间搜索文本发生更改,则该任务将被标记为无效,并且结果将被忽略。
如果您有使用旧的同步 API 的代码,则将现有代码转换为新的 API 相对简单。
func quickActionBar(_ quickActionBar: DSFQuickActionBar, itemsForSearchTermTask task: DSFQuickActionBar.SearchTask)
let results = countryNames.filter { $0.name.startsWith(task.searchTerm) }
task.complete(with: results)
}
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
}
}
func quickActionBar(_ quickActionBar: DSFQuickActionBar, viewForItem item: AnyHashable, searchTerm: String) -> NSView?
返回要在条目的行中显示的视图。 还提供了搜索词,以允许为搜索词自定义视图(例如,突出显示名称中的匹配项)
func quickActionBar(_ quickActionBar: DSFQuickActionBar, canSelectItem item: AnyHashable) -> Bool
当要选择一个条目时调用(例如,通过键盘导航或单击)。 如果不应选择此行,则返回 false(例如,它是一个分隔符)
func quickActionBar(_ quickActionBar: DSFQuickActionBar, didSelectItem item: AnyHashable)
当在列表中选择一个条目时调用。
// Swift
func quickActionBar(_ quickActionBar: DSFQuickActionBar, didActivateItem item: AnyHashable)
指示用户激活了结果列表中的一个条目。 “item”参数是用户选择的条目
func quickActionBarDidCancel(_ quickActionBar: DSFQuickActionBar)
如果用户取消了快速操作栏(例如,通过按 esc
键或单击栏外部)则调用
一个简单的 AppKit 示例,使用 Core Image Filters 作为 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 })
}
}
SwiftUI 实现是一个视图。 您像安装任何其他 SwiftUI 视图一样“安装”快速操作栏。 QuickActionBar
视图大小为零,并且不会在其安装的视图中显示内容。
QuickActionBar<IdentifyingObject, IdentifyingObjectView>
QuickActionBar 模板参数表示
IdentifyingObject
是对象的类型(例如,URL
)IdentifyingObjectView
是用于表示结果列表中的 IdentifyingObject
的视图类型(例如,Text
)您可以通过将 visible
参数设置为 true 来显示快速操作栏。
例如:-
@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)")
}
}
...
参数 | 描述 |
---|---|
location |
快速操作栏的放置位置 (.window, .screen) |
visible |
如果为 true,则在屏幕上显示快速操作栏 |
showKeyboardShortcuts |
为前 10 个可选项目显示键盘快捷键 |
requiredClickCount |
如果为 .single ,则仅要求用户单击一行即可激活它(默认为 .double ) |
barWidth |
显示的栏的宽度 |
searchTerm |
要使用的搜索词,在快速操作栏关闭时更新 |
selectedItem |
用户选择的条目 |
placeholderText |
当搜索词为空时,在快速操作栏中显示的文本 |
itemsForSearchTerm |
一个块,用于返回指定搜索词的条目 |
viewForItem |
一个块,用于返回要为指定条目显示的视图 |
一个简单的 macOS SwiftUI 示例,使用 Core Image Filters 作为内容。
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)
}
)
}
}
}
/// 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()
MIT 许可证。 您可以随意使用和滥用它,只需署名我的作品即可。 如果您在某个地方使用它,请告诉我,我很乐意听到!
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.