Skip to content

Instantly share code, notes, and snippets.

@boraseoksoon
Created May 7, 2023 00:55
Show Gist options
  • Save boraseoksoon/3da6f5f3773d659299d92b3bb0954e13 to your computer and use it in GitHub Desktop.
Save boraseoksoon/3da6f5f3773d659299d92b3bb0954e13 to your computer and use it in GitHub Desktop.
Spotlight App Search minimal UI + functionality
//
// ContentView.swift
// SpotlightAppSearch
//
// Created by seoksoon jang on 2023-05-06.
//
import SwiftUI
import CoreServices
struct ContentView: View {
@State private var searchText: String = ""
@State private var installedApps: [AppItem] = []
private var filteredApps: [AppItem] {
installedApps.filter { app in
searchText.isEmpty || app.name.localizedCaseInsensitiveContains(searchText)
}
}
var body: some View {
VStack {
TextField("Search apps", text: $searchText)
.padding(.horizontal)
AppListView(apps: filteredApps)
}
.task {
installedApps = await fetchInstalledApps()
}
}
func fetchInstalledApps() async -> [AppItem] {
let query = MDQueryCreate(kCFAllocatorDefault, "kMDItemContentTypeTree == 'com.apple.application-*'" as CFString, nil, nil)
MDQueryExecute(query, CFOptionFlags(kMDQuerySynchronous.rawValue))
let count = MDQueryGetResultCount(query)
lazy var apps: [AppItem] = []
try? await withThrowingTaskGroup(of: AppItem.self) { group in
for index in 0..<count {
group.addTask {
guard let rawPtr = MDQueryGetResultAtIndex(query, index),
let item = Unmanaged<MDItem>.fromOpaque(rawPtr).takeUnretainedValue() as MDItem?,
let path = MDItemCopyAttribute(item, kMDItemPath) as? String, path.hasSuffix(".app"),
let displayName = MDItemCopyAttribute(item, kMDItemDisplayName) as? String
else {
throw NSError(domain: "AppItemError", code: 1, userInfo: nil)
}
let url = URL(fileURLWithPath: path)
return AppItem(name: displayName, url: url)
}
}
for try await app in group {
apps.append(app)
}
}
return apps
}
}
struct AppRowView: View {
var app: AppItem
var body: some View {
HStack {
Image(nsImage: getAppIcon(for: app.url))
.resizable()
.scaledToFit()
Text(app.name)
Button(action: {
launchApp(at: app.url)
}) {
Text("click")
}
}
}
func getAppIcon(for url: URL) -> NSImage {
return NSWorkspace.shared.icon(forFile: url.path)
}
func launchApp(at url: URL) {
NSWorkspace.shared.open(url)
}
}
struct AppListView: View {
var apps: [AppItem]
var body: some View {
List(apps) { app in
AppRowView(app: app)
}
}
}
struct AppItem: Identifiable, Hashable {
var id = UUID()
var name: String
var url: URL
}
extension AppItem {
init?(url: URL) {
guard let appName = url.deletingPathExtension().lastPathComponent as String? else {
return nil
}
self.name = appName
self.url = url
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment