Skip to content
Open
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
44 changes: 44 additions & 0 deletions src/Shared/PresetsDisplay/TagKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// TagKey.swift
// Go Map!!
//
// Copyright © 2026 Bryce Cogswell. All rights reserved.
//

import UIKit

/// OSM tag key helpers shared by the POI editor.
enum TagKey {
private static let exactNameLikeKeys: Set<String> = ["name", "alt_name", "old_name"]

/// Keys that carry human-readable names and should use the same keyboard traits as `name`.
static func isNameLike(_ key: String) -> Bool {
guard !key.isEmpty else { return false }
if exactNameLikeKeys.contains(key) {
return true
}
return key.hasPrefix("name:")
}

static func autocapitalizationType(matchingNamePresetIn presets: [PresetDisplayKey])
-> UITextAutocapitalizationType
{
presets.first(where: { $0.tagKey == "name" })?.autocapitalizationType ?? .words
}

static func autocorrectType(matchingNamePresetIn presets: [PresetDisplayKey]) -> UITextAutocorrectionType {
presets.first(where: { $0.tagKey == "name" })?.autocorrectType ?? .no
}

static func applyNameLikeTraits(to textField: UITextField, presets: [PresetDisplayKey]) {
textField.autocapitalizationType = autocapitalizationType(matchingNamePresetIn: presets)
textField.autocorrectionType = autocorrectType(matchingNamePresetIn: presets)
textField.spellCheckingType = textField.autocorrectionType == .no ? .no : .default
}

static func applyNameLikeTraits(to textView: UITextView, presets: [PresetDisplayKey]) {
textView.autocapitalizationType = autocapitalizationType(matchingNamePresetIn: presets)
textView.autocorrectionType = autocorrectType(matchingNamePresetIn: presets)
textView.spellCheckingType = textView.autocorrectionType == .no ? .no : .default
}
}
8 changes: 8 additions & 0 deletions src/iOS/Go Map!!.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
021BC2222D7625FB004631C5 /* Panoramax.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 021BC2212D7625FB004631C5 /* Panoramax.storyboard */; };
021C6AB3168768C800FB17B0 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 021C6AB2168768C800FB17B0 /* MessageUI.framework */; };
021FADBB258591D000F6E1C0 /* PresetDisplayKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021FADBA258591D000F6E1C0 /* PresetDisplayKey.swift */; };
C8E2A0012F5C000100000001 /* TagKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E2A0022F5C000100000001 /* TagKey.swift */; };
021FADC0258594EE00F6E1C0 /* PresetDisplayValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021FADBF258594EE00F6E1C0 /* PresetDisplayValue.swift */; };
021FADC52585951E00F6E1C0 /* PresetDisplayGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021FADC42585951E00F6E1C0 /* PresetDisplayGroup.swift */; };
021FADCD25873C8200F6E1C0 /* PresetsDatabase+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021FADCC25873C8200F6E1C0 /* PresetsDatabase+Display.swift */; };
Expand Down Expand Up @@ -238,6 +239,7 @@
647F46CE2253EA4C00CEC482 /* MeasureDirectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F46CD2253EA4C00CEC482 /* MeasureDirectionViewModel.swift */; };
647F46D12253F08200CEC482 /* HeadingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F46D02253F08200CEC482 /* HeadingProvider.swift */; };
64C072FA226227D500598078 /* PresetKeyTagCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C072F9226227D500598078 /* PresetKeyTagCase.swift */; };
C8E2A0032F5C000100000001 /* TagKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E2A0042F5C000100000001 /* TagKeyTests.swift */; };
64D74BF32253DF49004FFD20 /* DirectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D74BF12253DF49004FFD20 /* DirectionViewController.swift */; };
64E21EB522651C06004605D7 /* OSMMapDataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E21EB422651C06004605D7 /* OSMMapDataTestCase.swift */; };
64E21EB822651F2D004605D7 /* XCTestCase+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E21EB722651F2D004605D7 /* XCTestCase+UserDefaults.swift */; };
Expand Down Expand Up @@ -392,6 +394,7 @@
021BC2212D7625FB004631C5 /* Panoramax.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Panoramax.storyboard; sourceTree = "<group>"; };
021C6AB2168768C800FB17B0 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
021FADBA258591D000F6E1C0 /* PresetDisplayKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetDisplayKey.swift; sourceTree = "<group>"; };
C8E2A0022F5C000100000001 /* TagKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagKey.swift; sourceTree = "<group>"; };
021FADBF258594EE00F6E1C0 /* PresetDisplayValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetDisplayValue.swift; sourceTree = "<group>"; };
021FADC42585951E00F6E1C0 /* PresetDisplayGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetDisplayGroup.swift; sourceTree = "<group>"; };
021FADCC25873C8200F6E1C0 /* PresetsDatabase+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PresetsDatabase+Display.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -600,6 +603,7 @@
647F46CD2253EA4C00CEC482 /* MeasureDirectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasureDirectionViewModel.swift; sourceTree = "<group>"; };
647F46D02253F08200CEC482 /* HeadingProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadingProvider.swift; sourceTree = "<group>"; };
64C072F9226227D500598078 /* PresetKeyTagCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetKeyTagCase.swift; sourceTree = "<group>"; };
C8E2A0042F5C000100000001 /* TagKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagKeyTests.swift; sourceTree = "<group>"; };
64D74BF12253DF49004FFD20 /* DirectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectionViewController.swift; sourceTree = "<group>"; };
64E21EB422651C06004605D7 /* OSMMapDataTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSMMapDataTestCase.swift; sourceTree = "<group>"; };
64E21EB722651F2D004605D7 /* XCTestCase+UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+UserDefaults.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1088,6 +1092,7 @@
021FADD625873D0100F6E1C0 /* PresetDisplayForFeature.swift */,
021FADC42585951E00F6E1C0 /* PresetDisplayGroup.swift */,
021FADBA258591D000F6E1C0 /* PresetDisplayKey.swift */,
C8E2A0022F5C000100000001 /* TagKey.swift */,
021FADD125873CC000F6E1C0 /* PresetDisplayKeyUserDefined.swift */,
021FADBF258594EE00F6E1C0 /* PresetDisplayValue.swift */,
021FADCC25873C8200F6E1C0 /* PresetsDatabase+Display.swift */,
Expand Down Expand Up @@ -1185,6 +1190,7 @@
children = (
64E21EB622651F0D004605D7 /* Helpers */,
64C072F9226227D500598078 /* PresetKeyTagCase.swift */,
C8E2A0042F5C000100000001 /* TagKeyTests.swift */,
64348CFA225E867800ADE7FB /* MeasureDirectionViewModelTestCase.swift */,
64348CF6225E867800ADE7FB /* Mocks */,
64348D02225E8E4300ADE7FB /* OsmNode_DirectionTestCase.swift */,
Expand Down Expand Up @@ -1640,6 +1646,7 @@
02CE3CE929919FC400DDACE0 /* MapPinButton.swift in Sources */,
EDDBA55326130287001E7D5C /* GpxViewController.swift in Sources */,
021FADBB258591D000F6E1C0 /* PresetDisplayKey.swift in Sources */,
C8E2A0012F5C000100000001 /* TagKey.swift in Sources */,
C381C30B263FE518003142BA /* PushPinView.swift in Sources */,
02CB2407265DB7CB00835F32 /* LevenshteinDistance.swift in Sources */,
C3004F54263A99DD006BF313 /* POIAttributesViewController.swift in Sources */,
Expand Down Expand Up @@ -1807,6 +1814,7 @@
files = (
64348CFE225E867800ADE7FB /* MeasureDirectionViewModelTestCase.swift in Sources */,
64C072FA226227D500598078 /* PresetKeyTagCase.swift in Sources */,
C8E2A0032F5C000100000001 /* TagKeyTests.swift in Sources */,
64348CFC225E867800ADE7FB /* MeasureDirectionViewModelDelegateMock.swift in Sources */,
64348CED225E7CD900ADE7FB /* GoMapTests.swift in Sources */,
64305F7423E723B200232BB9 /* LocationURLParserTestCase.swift in Sources */,
Expand Down
25 changes: 25 additions & 0 deletions src/iOS/GoMapTests/TagKeyTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// TagKeyTests.swift
// GoMapTests
//
// Copyright © 2026 Bryce Cogswell. All rights reserved.
//

@testable import Go_Map__
import XCTest

class TagKeyTests: XCTestCase {
func testIsNameLikePositiveCases() {
let positive = ["name", "name:en", "name:zh-Hans", "alt_name", "old_name"]
for key in positive {
XCTAssertTrue(TagKey.isNameLike(key), "expected name-like: \(key)")
}
}

func testIsNameLikeNegativeCases() {
let negative = ["namesake", "name_source", ""]
for key in negative {
XCTAssertFalse(TagKey.isNameLike(key), "expected not name-like: \"\(key)\"")
}
}
}
10 changes: 9 additions & 1 deletion src/iOS/POI/KeyValueTableCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,20 @@ class KeyValueTableCell: TextPairTableCell, PresetValueTextFieldOwner, UITextFie

func selectTextViewFor(key: String) {
// set text formatting options for text field
if let preset = keyValueCellOwner?.allPresetKeys.first(where: { key == $0.tagKey }) {
let presets = keyValueCellOwner?.allPresetKeys ?? []
if let preset = presets.first(where: { key == $0.tagKey }) {
if preset.type == .textarea {
useTextView()
if TagKey.isNameLike(key) {
if let textView = textView {
TagKey.applyNameLikeTraits(to: textView, presets: presets)
}
}
} else {
useTextField()
}
} else if TagKey.isNameLike(key) {
useTextField()
} else {
switch key {
case "note", "comment", "description", "fixme", "inscription", "source":
Expand Down
3 changes: 3 additions & 0 deletions src/iOS/POI/POIAllTagsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ class POIAllTagsViewController: UITableViewController, POIFeaturePickerDelegate,
cell.text1.autocapitalizationType = .none
cell.text1.spellCheckingType = .no
cell.text2.defaultInputAccessoryView = prevNextToolbar
if TagKey.isNameLike(kv.k) {
TagKey.applyNameLikeTraits(to: cell.text2, presets: allPresetKeys)
}

cell.isSet.backgroundColor = kv.k == "" || kv.v == "" ? nil : UIColor.systemBlue
return cell
Expand Down
3 changes: 3 additions & 0 deletions src/iOS/POI/POICommonTagsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ class POICommonTagsViewController: UITableViewController, UITextFieldDelegate, U
cell.presetKey = .key(presetKey)
cell.valueField.keyboardType = presetKey.keyboardType
cell.valueField.autocapitalizationType = presetKey.autocapitalizationType
if TagKey.isNameLike(presetKey.tagKey), presetKey.autocapitalizationType == .none {
TagKey.applyNameLikeTraits(to: cell.valueField, presets: allPresetKeys)
}

cell.valueField.removeTarget(self, action: nil, for: .allEvents)
cell.valueField.addTarget(self, action: #selector(textFieldReturn(_:)), for: .editingDidEndOnExit)
Expand Down
6 changes: 6 additions & 0 deletions src/iOS/POI/PresetValueTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ class PresetValueTextField: AutocompleteTextField, PanoramaxDelegate {
{
inputAccessoryView = TelephoneToolbar(forTextField: self, frame: frame)
}

if TagKey.isNameLike(key), preset.autocapitalizationType == .none {
TagKey.applyNameLikeTraits(to: self, presets: owner.allPresetKeys)
}
} else if TagKey.isNameLike(key) {
TagKey.applyNameLikeTraits(to: self, presets: owner.allPresetKeys)
} else {
switch key {
case "note", "comment", "description", "fixme", "inscription", "source":
Expand Down
Loading