I'm coding a Swift application for macOS. I'm a beginner in Swift so my problem might seem stupid to you, I apologize for that...
I have a type containing the following two String variables:
struct ShortcutInfo: Identifiable {
let id: String
let name: String
}
The variable I use the most is "name", containing a list of texts.
I tried to filter the list with a TextField. The filtered list should appear in the Picker.
@State var filteredShortcuts = ShortcutInfo(id: "", name: "").name.filter($0.lowercased().contains(searchText.lowercased()))
// body
TextField("", text: $searchText)
Picker("", selection: $selection) {
ForEach(filteredShortcuts) { item in
Text(item)
}
}
But I get the error "Cannot use instance member 'searchText' within property initializer; property initializers run before 'self' is available". What is wrong in my code?
Full code:
@State var shortcuts = [ShortcutInfo]()
@State var selection = ""
@State var searchText: String = ""
@State var filteredShortcuts = ShortcutInfo(id: "", name: "").name.filter($0.lowercased().contains(searchText.lowercased()))
// body
TextField("", text: $searchText)
Picker("", selection: $action1a) {
ForEach(filteredShortcuts) { item in
Text(item)
}
}
.task {
let shortcuts = (try? await loadShortcutNames()) ?? []
self.shortcuts = shortcuts
selection = shortcuts.first?.id ?? ""
}
//
func loadShortcutNames() async throws -> [ShortcutInfo] {
let process = Process()
process.executableURL = URL(filePath: "/usr/bin/shortcuts")
process.arguments = ["list", "--show-identifiers"]
let pipe = Pipe()
process.standardOutput = pipe
return try await withTaskCancellationHandler {
try process.run()
return try await pipe.fileHandleForReading.bytes.lines.compactMap { line in
guard let (_, name, id) = line.wholeMatch(of: /(.*) \(([A-Z0-9-]*)\)/)?.output else {
return nil
}
return ShortcutInfo(id: String(id), name: String(name))
}
.reduce(into: []) { $0.append($1) }
} onCancel: {
process.terminate()
}
}
struct ShortcutInfo: Identifiable {
let id: String
let name: String
}
extension Binding where Value == Int {
var toString: Binding<String> {
Binding<String>(
get: {
"\(wrappedValue)"
},
set: {
wrappedValue = Int($0) ?? 0
}
)
}
}
In addition, I am not sure of the validity of my code regarding the display of the list in the Picker.
Thanks in advance.
You cannot filter the text on the top level, you have to filter the text for example in the onChange
modifier
struct ContentView: View {
@State private var shortcuts = [ShortcutInfo]()
@State private var selection = "None"
@State private var searchText: String = ""
@State private var filteredShortcuts = [ShortcutInfo]()
var body: some View {
VStack {
TextField("", text: $searchText)
Picker("", selection: $selection) {
Text("Nothing selected").tag("None")
ForEach(filteredShortcuts) { Text($0.name) }
}
.onChange(of: searchText) { _, newValue in
filteredShortcuts = shortcuts.filter{$0.name.localizedStandardContains(searchText)}
selection = filteredShortcuts.first?.id ?? "None"
}
}
.task {
self.shortcuts = (try? await loadShortcutNames()) ?? []
selection = "None"
}
}
func loadShortcutNames() async throws -> [ShortcutInfo] {
let process = Process()
process.executableURL = URL(filePath: "/usr/bin/shortcuts")
process.arguments = ["list", "--show-identifiers"]
let pipe = Pipe()
process.standardOutput = pipe
return try await withTaskCancellationHandler {
try process.run()
return try await pipe.fileHandleForReading.bytes.lines.compactMap { line in
guard let (_, name, id) = line.wholeMatch(of: /(.*) \(([A-Z0-9-]*)\)/)?.output else {
return nil
}
return ShortcutInfo(id: String(id), name: String(name))
}
.reduce(into: []) { $0.append($1) }
} onCancel: {
process.terminate()
}
}
}