Skip to content

Commit b61fecb

Browse files
committed
Fix a bug and add search to the location simulator
1 parent f9c392f commit b61fecb

2 files changed

Lines changed: 125 additions & 28 deletions

File tree

StikJIT/Views/MapSelectionView.swift

Lines changed: 123 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,33 @@ extension CLLocationCoordinate2D: Equatable {
1515
}
1616
}
1717

18+
// MARK: - Search Completer
19+
20+
@MainActor
21+
final class LocationSearchCompleter: NSObject, ObservableObject, MKLocalSearchCompleterDelegate {
22+
@Published var results: [MKLocalSearchCompletion] = []
23+
private let completer = MKLocalSearchCompleter()
24+
25+
override init() {
26+
super.init()
27+
completer.delegate = self
28+
completer.resultTypes = [.address, .pointOfInterest]
29+
}
30+
31+
func update(query: String) {
32+
completer.queryFragment = query
33+
}
34+
35+
nonisolated func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
36+
let results = completer.results
37+
Task { @MainActor in self.results = results }
38+
}
39+
40+
nonisolated func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
41+
Task { @MainActor in self.results = [] }
42+
}
43+
}
44+
1845
struct LocationSimulationView: View {
1946
// Serial queue: simulate_location and clear_simulated_location share C global
2047
// state — serialising all calls eliminates the use-after-free race.
@@ -31,6 +58,9 @@ struct LocationSimulationView: View {
3158
@State private var alertTitle = ""
3259
@State private var alertMessage = ""
3360

61+
@State private var searchText = ""
62+
@StateObject private var searchCompleter = LocationSearchCompleter()
63+
3464
private var pairingFilePath: String {
3565
URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path()
3666
}
@@ -70,32 +100,94 @@ struct LocationSimulationView: View {
70100
}
71101
}
72102

73-
VStack(spacing: 12) {
74-
if let coord = coordinate {
75-
Text(String(format: "%.6f, %.6f", coord.latitude, coord.longitude))
76-
.font(.footnote.monospaced())
77-
.foregroundStyle(.secondary)
103+
VStack(spacing: 0) {
104+
if !searchCompleter.results.isEmpty {
105+
if #available(iOS 26, *) {
106+
List(searchCompleter.results.prefix(5), id: \.self) { result in
107+
Button {
108+
selectSearchResult(result)
109+
} label: {
110+
VStack(alignment: .leading, spacing: 2) {
111+
Text(result.title)
112+
.font(.subheadline)
113+
if !result.subtitle.isEmpty {
114+
Text(result.subtitle)
115+
.font(.caption)
116+
.foregroundStyle(.secondary)
117+
}
118+
}
119+
}
120+
}
121+
.listStyle(.plain)
122+
.frame(maxHeight: 350)
123+
.scrollDisabled(true)
124+
.glassEffect(in: .rect(cornerRadius: 12))
125+
.padding(.horizontal, 16)
126+
.padding(.top, 8)
127+
} else {
128+
List(searchCompleter.results.prefix(5), id: \.self) { result in
129+
Button {
130+
selectSearchResult(result)
131+
} label: {
132+
VStack(alignment: .leading, spacing: 2) {
133+
Text(result.title)
134+
.font(.subheadline)
135+
if !result.subtitle.isEmpty {
136+
Text(result.subtitle)
137+
.font(.caption)
138+
.foregroundStyle(.secondary)
139+
}
140+
}
141+
}
142+
}
143+
.listStyle(.plain)
144+
.frame(maxHeight: 350)
145+
.scrollDisabled(true)
146+
.padding(.horizontal, 16)
147+
.padding(.top, 8)
148+
}
149+
}
150+
151+
Spacer()
78152

79-
HStack(spacing: 12) {
80-
Button("Stop", action: clear)
81-
.buttonStyle(.bordered)
82-
.tint(.red)
83-
.disabled(!pairingExists || isBusy)
153+
// Bottom controls
154+
VStack(spacing: 12) {
155+
if let coord = coordinate {
156+
Text(String(format: "%.6f, %.6f", coord.latitude, coord.longitude))
157+
.font(.footnote.monospaced())
158+
.foregroundStyle(.secondary)
159+
160+
HStack(spacing: 12) {
161+
Button("Stop", action: clear)
162+
.buttonStyle(.bordered)
163+
.tint(.red)
164+
.disabled(!pairingExists || isBusy)
84165

85-
Button("Simulate Location", action: simulate)
86-
.buttonStyle(.borderedProminent)
87-
.disabled(!pairingExists || isBusy)
166+
Button("Simulate Location", action: simulate)
167+
.buttonStyle(.borderedProminent)
168+
.disabled(!pairingExists || isBusy)
169+
}
170+
} else {
171+
Text("Tap map to drop pin")
172+
.font(.subheadline)
173+
.foregroundStyle(.secondary)
88174
}
89-
} else {
90-
Text("Tap map to drop pin")
91-
.font(.subheadline)
92-
.foregroundStyle(.secondary)
93175
}
176+
.padding(.bottom, 24)
177+
.padding(.horizontal, 16)
94178
}
95-
.padding(.bottom, 24)
96-
.padding(.horizontal, 16)
97179
}
98180
.navigationBarTitleDisplayMode(.inline)
181+
.toolbar {
182+
ToolbarItem(placement: .topBarTrailing) {
183+
TextField("Search location...", text: $searchText)
184+
.padding(.leading, 6)
185+
.autocorrectionDisabled()
186+
.onChange(of: searchText) { _, newValue in
187+
searchCompleter.update(query: newValue)
188+
}
189+
}
190+
}
99191
.alert(alertTitle, isPresented: $showAlert) {
100192
Button("OK", role: .cancel) { }
101193
} message: {
@@ -107,6 +199,18 @@ struct LocationSimulationView: View {
107199
}
108200
}
109201

202+
private func selectSearchResult(_ result: MKLocalSearchCompletion) {
203+
searchText = ""
204+
searchCompleter.results = []
205+
206+
let request = MKLocalSearch.Request(completion: result)
207+
MKLocalSearch(request: request).start { response, _ in
208+
if let item = response?.mapItems.first {
209+
coordinate = item.placemark.coordinate
210+
}
211+
}
212+
}
213+
110214
private func simulate() {
111215
guard pairingExists, let coord = coordinate, !isBusy else { return }
112216
isBusy = true

StikJIT/Views/SettingsView.swift

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,8 @@ struct SettingsView: View {
149149
Spacer()
150150
TextField("10.7.0.1", text: $customTargetIP)
151151
.multilineTextAlignment(.trailing)
152-
.keyboardType(.decimalPad)
153-
.toolbar {
154-
ToolbarItemGroup(placement: .keyboard) {
155-
Spacer()
156-
Button("Done") {
157-
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
158-
}
159-
}
160-
}
152+
.keyboardType(.numbersAndPunctuation)
153+
.submitLabel(.done)
161154
}
162155
Button { openAppFolder() } label: {
163156
Label("App Folder", systemImage: "folder")

0 commit comments

Comments
 (0)