1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import SwiftUI
/// Reusable Form rows for location search and selection.
/// Renders a confirmed-location row when `resolvedLocation` is set,
/// or a photo suggestion + debounced search field + results list when it isn't.
struct LocationPickerRows: View {
@Binding var resolvedLocation: (h3: String, name: String, address: [String: AnyCodable]?)?
let photoLocationResult: NominatimResult?
var photoLocationLabel: String = "Use photo location"
let onSelectLocation: (NominatimResult) -> Void
@State private var locationQuery = ""
@State private var locationSuggestions: [NominatimResult] = []
@State private var isSearchingLocation = false
@State private var searchTask: Task<Void, Never>?
var body: some View {
if let loc = resolvedLocation {
HStack {
Label(loc.name, systemImage: "mappin.and.ellipse")
.font(.subheadline)
.lineLimit(1)
Spacer()
Button {
resolvedLocation = nil
} label: {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.secondary)
}
}
} else {
if let photoLoc = photoLocationResult {
Button { selectResult(photoLoc) } label: {
HStack(spacing: 10) {
Image(systemName: "location.fill")
.foregroundStyle(.secondary)
.frame(width: 20)
VStack(alignment: .leading, spacing: 1) {
Text(photoLocationLabel)
.font(.subheadline)
Text(photoLoc.name)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
.foregroundStyle(.primary)
}
HStack {
Image(systemName: "magnifyingglass")
.foregroundStyle(.secondary)
TextField("Search for a location...", text: $locationQuery)
.textInputAutocapitalization(.never)
.onChange(of: locationQuery) {
searchTask?.cancel()
let query = locationQuery
searchTask = Task {
try? await Task.sleep(for: .milliseconds(300))
guard !Task.isCancelled else { return }
await performSearch(query: query)
}
}
if isSearchingLocation {
ProgressView().controlSize(.small)
}
}
ForEach(locationSuggestions, id: \.placeId) { result in
Button { selectResult(result) } label: {
VStack(alignment: .leading, spacing: 2) {
Text(result.name)
.font(.subheadline)
.foregroundStyle(.primary)
if let context = result.context {
Text(context)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
}
}
private func selectResult(_ result: NominatimResult) {
onSelectLocation(result)
locationQuery = ""
locationSuggestions = []
}
private func performSearch(query: String) async {
isSearchingLocation = true
defer { isSearchingLocation = false }
locationSuggestions = await LocationServices.searchLocation(query: query)
}
}