Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/GoMap.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@
C365A563263FC53F007DEC94 /* TurnRestrictHwyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C365A562263FC53F007DEC94 /* TurnRestrictHwyView.swift */; };
C365A569263FCC9C007DEC94 /* TapAndDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = C365A568263FCC9C007DEC94 /* TapAndDragGesture.swift */; };
C381C30B263FE518003142BA /* PushPinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C381C30A263FE518003142BA /* PushPinView.swift */; };
C3A1D0022F9A00010000A001 /* Wikidata.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A1D0012F9A00010000A001 /* Wikidata.swift */; };
C3D0969C263CBC1100E3B129 /* POIFeaturePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D0969B263CBC1100E3B129 /* POIFeaturePickerViewController.swift */; };
C3D096AC263D48F700E3B129 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D096AB263D48F700E3B129 /* AppDelegate.swift */; };
C3D096AE263D4BFB00E3B129 /* UploadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D096AD263D4BFB00E3B129 /* UploadViewController.swift */; };
Expand Down Expand Up @@ -661,6 +662,7 @@
C381C305263FD0CA003142BA /* VectorMath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VectorMath.swift; sourceTree = "<group>"; };
C381C306263FD0E8003142BA /* UndoManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UndoManager.swift; sourceTree = "<group>"; };
C381C30A263FE518003142BA /* PushPinView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushPinView.swift; sourceTree = "<group>"; };
C3A1D0012F9A00010000A001 /* Wikidata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wikidata.swift; sourceTree = "<group>"; };
C3D0969B263CBC1100E3B129 /* POIFeaturePickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = POIFeaturePickerViewController.swift; sourceTree = "<group>"; };
C3D0969F263CD4BD00E3B129 /* PersistentWebCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentWebCache.swift; sourceTree = "<group>"; };
C3D096A2263CEBBC00E3B129 /* EditorMapLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorMapLayer.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1280,6 +1282,7 @@
02771D702BFC15C300FE9B79 /* UserFileList.swift */,
C381C305263FD0CA003142BA /* VectorMath.swift */,
C3D096FA263ED2EC00E3B129 /* VoiceAnnouncement.swift */,
C3A1D0012F9A00010000A001 /* Wikidata.swift */,
C341E1522639DECC0022E983 /* WikiPage.swift */,
);
name = Utility;
Expand Down Expand Up @@ -1761,6 +1764,7 @@
023320A22BE07B2000E3311A /* DataOverlayLayer.swift in Sources */,
02F2449A264F635800C42E41 /* PathUtil.swift in Sources */,
C341E1532639DECC0022E983 /* WikiPage.swift in Sources */,
C3A1D0022F9A00010000A001 /* Wikidata.swift in Sources */,
02B5669D294F9D7E00710632 /* MagnifyingGlass.swift in Sources */,
0286EAA62656FEAC007CFEE9 /* EditorMapLayer+Ocean.swift in Sources */,
023320A42BE1DB9900E3311A /* DPadView.swift in Sources */,
Expand Down
45 changes: 45 additions & 0 deletions src/POI/PresetValueTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class PresetValueTextField: AutocompleteTextField, PanoramaxDelegate {
textColor = nil

if key != "",
!isWikidataKey,
PresetsDatabase.shared.eligibleForAutocomplete(key)
{
var set: Set<String> = PresetsDatabase.shared.allTagValuesForKey(key)
Expand Down Expand Up @@ -194,6 +195,50 @@ class PresetValueTextField: AutocompleteTextField, PanoramaxDelegate {
text = value
updateAssociatedContent()
notifyValueChange(ended: true)

wikidataSearchTask?.cancel()
wikidataSearchText = ""
completionLabels = [:]
}

private var wikidataSearchTask: Task<Void, Never>?
private var wikidataSearchText = ""

private var isWikidataKey: Bool {
return OsmTags.isKey(key, variantOf: "wikidata")
}

override func updateAutocomplete(for text: String) {
guard isWikidataKey else {
super.updateAutocomplete(for: text)
return
}
let text = text.trimmingCharacters(in: .whitespacesAndNewlines)
if text == wikidataSearchText {
showCompletions(filteredCompletions)
return
}
wikidataSearchText = text
wikidataSearchTask?.cancel()
guard text != "", !Wikidata.isQID(text) else {
showCompletions([])
return
}
wikidataSearchTask = Task { @MainActor [weak self] in
try? await Task.sleep(nanoseconds: 300_000000)
guard !Task.isCancelled,
let items = try? await Wikidata.search(text,
language: PresetLanguages.preferredPresetLanguageCode()),
!Task.isCancelled,
let self,
self.isEditing
else { return }
for item in items {
let subtitle = [item.id, item.description].compactMap { $0 }.joined(separator: " · ")
self.completionLabels[item.id] = (title: item.label, subtitle: subtitle)
}
self.showCompletions(items.map { $0.id })
}
}

// MARK: Accessory buttons
Expand Down
28 changes: 22 additions & 6 deletions src/Shared/CustomViews/AutocompleteTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ class AutocompleteTextField: UITextField, UITableViewDataSource, UITableViewDele
}
}

var completionLabels: [String: (title: String, subtitle: String?)] = [:]

func showCompletions(_ values: [String]) {
filteredCompletions = values
updateCompletionTableView()
}

func updateAutocomplete(for text: String) {
let text = text.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
// filter completion list by current text
Expand Down Expand Up @@ -241,15 +248,24 @@ class AutocompleteTextField: UITextField, UITableViewDataSource, UITableViewDele
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
// shouldn't need this check but we got an out-of-range crash report
let value = indexPath.row < filteredCompletions.count ? filteredCompletions[indexPath.row] : ""
let label = completionLabels[value]

let cellIdentifier = label != nil ? "SubtitleCell" : "Cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
?? UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
?? UITableViewCell(style: label != nil ? .subtitle : .default, reuseIdentifier: cellIdentifier)
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: .body)
// shouldn't need this check but we got an out-of-range crash report
if indexPath.row < filteredCompletions.count {
cell.textLabel?.text = filteredCompletions[indexPath.row]
if let label {
cell.textLabel?.text = label.title
cell.detailTextLabel?.text = label.subtitle
if #available(iOS 13.0, *) {
cell.detailTextLabel?.textColor = .secondaryLabel
} else {
cell.detailTextLabel?.textColor = .gray
}
} else {
cell.textLabel?.text = ""
cell.textLabel?.text = value
}
return cell
}
Expand Down
61 changes: 61 additions & 0 deletions src/Shared/Utility/Wikidata.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Wikidata.swift
// Go Map!!
//
// Copyright © 2026 Bryce Cogswell. All rights reserved.
//

import Foundation

struct WikidataItem {
let id: String
let label: String
let description: String?
}

enum Wikidata {
private static let apiURL = URL(string: "https://www.wikidata.org/w/api.php")!

static func isQID(_ value: String) -> Bool {
return value.range(of: "^Q[1-9][0-9]*$", options: .regularExpression) != nil
}

static func search(_ text: String, language: String) async throws -> [WikidataItem] {
let language = language.lowercased()
let results = try await search(text, wikidataLanguage: language)
if results == nil,
let base = language.split(separator: "-").first,
base != language
{
return try await search(text, wikidataLanguage: String(base)) ?? []
}
return results ?? []
}

private static func search(_ text: String, wikidataLanguage language: String) async throws -> [WikidataItem]? {
let url = apiURL.appendingQueryItems([
"action": "wbsearchentities",
"format": "json",
"formatversion": "2",
"type": "item",
"limit": "10",
"search": text,
"language": language,
"uselang": language
])
let data = try await URLSession.shared.data(with: url)
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { return [] }
if let error = json["error"] as? [String: Any],
error["code"] as? String == "badvalue"
{
return nil
}
guard let results = json["search"] as? [[String: Any]] else { return [] }
return results.compactMap { result in
guard let id = result["id"] as? String else { return nil }
return WikidataItem(id: id,
label: result["label"] as? String ?? id,
description: result["description"] as? String)
}
}
}
Loading