Got an answer on StackOverflow - https://stackoverflow.com/a/63027052/515585, I missed the docs for GridItem: https://developer.apple.com/documentation/swiftui/griditem
It also has a spacing parameter that controls the spacing between items, the documentation on grid should be clearer on the meaning of its spacing is not really the spacing between the grid and the next item, but the vertical spacing between grid rows.
Post
Replies
Boosts
Views
Activity
I'm seeing the same. It works but there is no semantic search.
Asked a similar question here: https://developer.apple.com/forums/thread/767355#767355021
Created an separate sample app, pretty sure this feature is not working as per video.
import SwiftUI
import CoreSpotlight
import UniformTypeIdentifiers
@main
struct SemanticSearchApp: App {
init() {
// Prepare Core Spotlight for searching
CSUserQuery.prepare()
// Index sample items when app launches
indexSampleItems()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
private func indexSampleItems() {
let items = [
("windsurfing-1", "The Best Windsurfing Carmel County", "Learn about the best windsurfing spots in Carmel County"),
("windsurfing-2", "Windsurfing Lessons", "Professional windsurfing lessons for beginners"),
("windsurfing-3", "Sailboarding Lessons", "Expert sailboarding instruction in Carmel")
].map { (id, title, description) -> CSSearchableItem in
let attributeSet = CSSearchableItemAttributeSet(contentType: .text)
attributeSet.title = title
attributeSet.contentDescription = description
return CSSearchableItem(
uniqueIdentifier: id,
domainIdentifier: "com.example.windsurfing",
attributeSet: attributeSet
)
}
// Use batch indexing for better performance
let index = CSSearchableIndex(name: "SpotlightSearchSample")
index.fetchLastClientState { state, error in
if error == nil {
index.beginBatch()
index.indexSearchableItems(items) { error in
if let error = error {
print("Indexing error: \(error.localizedDescription)")
}
}
// Create new state data (could be a timestamp or counter)
let newState = "batch1".data(using: .utf8) ?? Data()
index.endBatch(withClientState: newState) { error in
if let error = error {
print("Batch error: \(error.localizedDescription)")
}
}
}
}
}
}
struct ContentView: View {
@State private var query: String = ""
@State private var searchResults: [SearchResult] = []
@State private var suggestions: [String] = []
@State private var allItems: [SearchResult] = [
SearchResult(
id: "windsurfing-1",
title: "The Best Windsurfing Carmel County",
description: "Learn about the best windsurfing spots in Carmel County"
),
SearchResult(
id: "windsurfing-2",
title: "Windsurfing Lessons",
description: "Professional windsurfing lessons for beginners"
),
SearchResult(
id: "windsurfing-3",
title: "Sailboarding Lessons",
description: "Expert sailboarding instruction in Carmel"
)
]
var body: some View {
NavigationStack {
List(query.isEmpty ? allItems : searchResults) { result in
VStack(alignment: .leading) {
Text(result.title)
.font(.headline)
Text(result.description)
.font(.subheadline)
.foregroundColor(.gray)
}
}
.navigationTitle("Search Playground")
.searchable(
text: $query,
prompt: "Search items",
suggestions: {
ForEach(suggestions, id: \.self) { suggestion in
Text(suggestion)
.searchCompletion(suggestion)
}
}
)
.onChange(of: query) { newValue in
performSearch(newValue)
}
}
.padding()
.frame(minWidth: 400, minHeight: 500)
}
private func performSearch(_ searchText: String) {
guard !searchText.isEmpty else {
searchResults = []
suggestions = []
return
}
let queryContext = CSUserQueryContext()
queryContext.fetchAttributes = ["title", "contentDescription"]
queryContext.maxSuggestionCount = 5
queryContext.enableRankedResults = true
let searchQuery = CSUserQuery(userQueryString: searchText, userQueryContext: queryContext)
Task {
do {
var newResults: [SearchResult] = []
var newSuggestions: [String] = []
for try await element in searchQuery.responses {
switch element {
case .item(let queryItem):
if let title = queryItem.item.attributeSet.title,
let description = queryItem.item.attributeSet.contentDescription {
let result = SearchResult(
id: queryItem.item.uniqueIdentifier,
title: title,
description: description
)
newResults.append(result)
}
case .suggestion(let suggestion):
newSuggestions.append(String(suggestion.suggestion.localizedAttributedSuggestion.characters))
@unknown default:
break
}
}
await MainActor.run {
self.searchResults = newResults
self.suggestions = newSuggestions
}
} catch {
print("Search error: \(error.localizedDescription)")
}
}
}
}
struct SearchResult: Identifiable {
let id: String
let title: String
let description: String
}