From 191668cf1821f0fb91386a5d8dd9b23cdc63f75a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 30 May 2026 15:44:00 +0000 Subject: [PATCH] Add in-search toggle to disable NSI brand suggestions Introduce includeNSISuggestions user preference (default on) and wire it through preset search so NSI presets and remote brand logos are omitted when disabled. Place a Brand suggestions switch below search results in the feature picker; keep includeNSI matching for existing tagged objects. Co-authored-by: Tobias --- .../PresetsDatabase/PresetsDatabase.swift | 16 ++- .../PresetsDatabase+Display.swift | 6 +- src/Shared/UserPrefs.swift | 7 ++ src/iOS/POI/POICommonTagsViewController.swift | 6 +- .../POI/POIFeaturePickerViewController.swift | 117 +++++++++++++----- 5 files changed, 115 insertions(+), 37 deletions(-) diff --git a/src/Shared/PresetsDatabase/PresetsDatabase.swift b/src/Shared/PresetsDatabase/PresetsDatabase.swift index e8cf58973..d751a1642 100644 --- a/src/Shared/PresetsDatabase/PresetsDatabase.swift +++ b/src/Shared/PresetsDatabase/PresetsDatabase.swift @@ -211,7 +211,10 @@ final class PresetsDatabase { var localRegion = RegionInfoForLocation.none var stdLocal: [PresetFeature] = [] var nsiLocal: [PresetFeature] = [] - func enumeratePresetsAndNsiIn(region: RegionInfoForLocation, using block: (_ feature: PresetFeature) -> Void) { + func enumeratePresetsAndNsiIn(region: RegionInfoForLocation, + includeNSI: Bool = UserPrefs.shared.includeNSISuggestionsEnabled, + using block: (_ feature: PresetFeature) -> Void) + { if region != localRegion { // update cache with the current region localRegion = region @@ -221,8 +224,10 @@ final class PresetsDatabase { for v in stdLocal { block(v) } - for v in nsiLocal { - block(v) + if includeNSI { + for v in nsiLocal { + block(v) + } } } @@ -302,10 +307,11 @@ final class PresetsDatabase { func featuresMatchingSearchText(_ searchText: String?, geometry: GEOMETRY, - location: RegionInfoForLocation) -> [(PresetFeature, Int)] + location: RegionInfoForLocation, + includeNSI: Bool = UserPrefs.shared.includeNSISuggestionsEnabled) -> [(PresetFeature, Int)] { var list = [(PresetFeature, Int)]() - enumeratePresetsAndNsiIn(region: location, using: { feature in + enumeratePresetsAndNsiIn(region: location, includeNSI: includeNSI, using: { feature in guard let score = feature.matchesSearchText(searchText, geometry: geometry) else { diff --git a/src/Shared/PresetsDisplay/PresetsDatabase+Display.swift b/src/Shared/PresetsDisplay/PresetsDatabase+Display.swift index 5150a9874..99c4124af 100644 --- a/src/Shared/PresetsDisplay/PresetsDatabase+Display.swift +++ b/src/Shared/PresetsDisplay/PresetsDatabase+Display.swift @@ -24,7 +24,8 @@ extension PresetsDatabase { func featuresInCategory(_ category: PresetCategory?, matching searchText: String, geometry: GEOMETRY, - location: RegionInfoForLocation) -> [PresetFeature] + location: RegionInfoForLocation, + includeNSI: Bool = UserPrefs.shared.includeNSISuggestionsEnabled) -> [PresetFeature] { var list = [(feature: PresetFeature, score: Int)]() if let category = category { @@ -37,7 +38,8 @@ extension PresetsDatabase { list = Self.shared.featuresMatchingSearchText( searchText, geometry: geometry, - location: location) + location: location, + includeNSI: includeNSI) } list.sort(by: { obj1, obj2 -> Bool in if obj1.score != obj2.score { diff --git a/src/Shared/UserPrefs.swift b/src/Shared/UserPrefs.swift index b508e36e9..bf1915319 100644 --- a/src/Shared/UserPrefs.swift +++ b/src/Shared/UserPrefs.swift @@ -125,6 +125,8 @@ final class UserPrefs { let currentBasemapSelection = Pref(key: "BasemapSelectionId") // POI presets + /// When false, brand/chain (NSI) presets are omitted from preset search and logo downloads. + let includeNSISuggestions = Pref(key: "includeNSISuggestions", ubiquitous: true) let userDefinedPresetKeys = Pref(key: "userDefinedPresetKeys", ubiquitous: true) let userDefinedFeatures = Pref(key: "userDefinedFeatures", ubiquitous: true) let preferredUnitsForKeys = Pref<[String: String]>(key: "preferredUnitsForKeys", ubiquitous: true) @@ -217,4 +219,9 @@ final class UserPrefs { case .POINT: return mostRecentTypes_point } } + + /// Name Suggestion Index brand/chain presets in search (default on for parity with prior releases). + var includeNSISuggestionsEnabled: Bool { + includeNSISuggestions.value ?? true + } } diff --git a/src/iOS/POI/POICommonTagsViewController.swift b/src/iOS/POI/POICommonTagsViewController.swift index cd25d81d5..a6d40ea41 100644 --- a/src/iOS/POI/POICommonTagsViewController.swift +++ b/src/iOS/POI/POICommonTagsViewController.swift @@ -414,7 +414,11 @@ class POICommonTagsViewController: UITableViewController, UITextFieldDelegate, U cell.valueField.rightViewMode = .always } - if let icon = currentFeature?.nsiLogo(callback: setupIcon) { + if UserPrefs.shared.includeNSISuggestionsEnabled, + let icon = currentFeature?.nsiLogo(callback: setupIcon) + { + setupIcon(icon) + } else if let icon = currentFeature?.iconUnscaled?.withRenderingMode(.alwaysTemplate) { setupIcon(icon) } else { cell.valueField.rightView = nil diff --git a/src/iOS/POI/POIFeaturePickerViewController.swift b/src/iOS/POI/POIFeaturePickerViewController.swift index 161003f33..4ec528ca9 100644 --- a/src/iOS/POI/POIFeaturePickerViewController.swift +++ b/src/iOS/POI/POIFeaturePickerViewController.swift @@ -25,6 +25,7 @@ class FeaturePickerCell: UITableViewCell { private var mostRecentArray: [PresetFeature] = [] private var mostRecentMaximum = 0 +private let nsiToggleCellReuseId = "NsiToggleCell" class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate { private var featureList: [PresetFeatureOrCategory] = [] @@ -54,12 +55,28 @@ class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate return geometry } + private var isSearching: Bool { + (searchBar.text?.count ?? 0) > 0 + } + + private var includeNSI: Bool { + UserPrefs.shared.includeNSISuggestionsEnabled + } + override func viewDidLoad() { super.viewDidLoad() tableView.estimatedRowHeight = 44.0 // or could use UITableViewAutomaticDimension; tableView.rowHeight = UITableView.automaticDimension + UserPrefs.shared.includeNSISuggestions.onChange.subscribe(self) { [weak self] _ in + guard let self else { return } + if self.isSearching { + self.refreshSearchResults() + } + self.tableView.reloadData() + } + let geometry = currentSelectionGeometry() Self.loadMostRecent(forGeometry: geometry) @@ -76,16 +93,16 @@ class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate } override func numberOfSections(in tableView: UITableView) -> Int { - if (searchBar.text?.count ?? 0) > 0 { - // only show one section when searching - return 1 + if isSearching { + // search results + in-panel NSI toggle + return 2 } return isTopLevel ? 2 : 1 } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - if isTopLevel { - return section == 0 && (searchBar.text?.count ?? 0) == 0 + if isTopLevel, !isSearching { + return section == 0 ? NSLocalizedString("Most recent", comment: "") : NSLocalizedString("All choices", comment: "") } else { @@ -94,7 +111,7 @@ class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate } override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - if isTopLevel, section == 1 { + if isTopLevel, !isSearching, section == 1 { let countryCode = AppDelegate.shared.mainView.currentRegion.country let locale = NSLocale.current as NSLocale let countryName = locale.displayName(forKey: .countryCode, value: countryCode) ?? "" @@ -113,8 +130,11 @@ class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if (searchBar.text?.count ?? 0) > 0 { - return searchArray.count + if isSearching { + if section == 0 { + return searchArray.count + } + return 1 } else if isTopLevel, section == 0 { // showing most recent list let count = mostRecentArray.count @@ -130,6 +150,10 @@ class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if isSearching, indexPath.section == 1 { + return nsiToggleCell(for: tableView, at: indexPath) + } + let feature: PresetFeature if searchArray.count > 0 { feature = searchArray[indexPath.row] @@ -149,17 +173,7 @@ class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate } } - // If its an NSI feature then retrieve the logo icon - let icon = feature.nsiLogo(callback: { img in - // If it completes later then update any cells using it - for cell in self.tableView.visibleCells { - if let cell = cell as? FeaturePickerCell, - cell.featureID == feature.featureID - { - cell.pickerImage.image = img - } - } - }) + let icon = pickerIcon(for: feature) let brand = "☆ " let tabController = tabBarController as? POITabBarController let geometry = currentSelectionGeometry() @@ -168,9 +182,9 @@ class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate tags: tabController?.keyValueDict, geometry: geometry, location: AppDelegate.shared.mainView.currentRegion, - includeNSI: true) + includeNSI: includeNSI) let cell = tableView.dequeueReusableCell(withIdentifier: "FinalCell", for: indexPath) as! FeaturePickerCell - cell.title.text = feature.nsiSuggestion ? (brand + feature.friendlyName()) : feature.friendlyName() + cell.title.text = includeNSI && feature.nsiSuggestion ? (brand + feature.friendlyName()) : feature.friendlyName() cell.pickerImage.image = icon if #available(iOS 13.0, *) { cell.pickerImage.tintColor = UIColor.label @@ -210,6 +224,10 @@ class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if isSearching, indexPath.section == 1 { + tableView.deselectRow(at: indexPath, animated: true) + return + } if searchArray.count != 0 { let feature = searchArray[indexPath.row] updateTags(with: feature) @@ -242,20 +260,61 @@ class POIFeaturePickerViewController: UITableViewController, UISearchBarDelegate func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { if searchText.count == 0 { - // no search searchArray = [] } else { - // searching - let geometry = currentSelectionGeometry() - searchArray = PresetsDatabase.shared.featuresInCategory( - parentCategory, - matching: searchText, - geometry: geometry, - location: AppDelegate.shared.mainView.currentRegion) + refreshSearchResults() } tableView.reloadData() } + private func refreshSearchResults() { + let geometry = currentSelectionGeometry() + searchArray = PresetsDatabase.shared.featuresInCategory( + parentCategory, + matching: searchBar.text ?? "", + geometry: geometry, + location: AppDelegate.shared.mainView.currentRegion, + includeNSI: includeNSI) + } + + private func pickerIcon(for feature: PresetFeature) -> UIImage? { + if includeNSI { + return feature.nsiLogo(callback: { img in + for cell in self.tableView.visibleCells { + if let cell = cell as? FeaturePickerCell, + cell.featureID == feature.featureID + { + cell.pickerImage.image = img + } + } + }) + } + return feature.iconUnscaled?.withRenderingMode(.alwaysTemplate) + } + + private func nsiToggleCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: nsiToggleCellReuseId) + ?? UITableViewCell(style: .default, reuseIdentifier: nsiToggleCellReuseId) + cell.selectionStyle = .none + cell.textLabel?.text = NSLocalizedString( + "Brand suggestions", + comment: "Toggle below preset search results to include Name Suggestion Index chain/brand presets") + cell.textLabel?.numberOfLines = 0 + let toggle = UISwitch() + toggle.isOn = includeNSI + toggle.addTarget(self, action: #selector(nsiSuggestionsToggled(_:)), for: .valueChanged) + cell.accessoryView = toggle + return cell + } + + @objc private func nsiSuggestionsToggled(_ sender: UISwitch) { + UserPrefs.shared.includeNSISuggestions.value = sender.isOn + if isSearching { + refreshSearchResults() + tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + } + } + @IBAction func configure(_ sender: Any) { let alert = UIAlertController( title: NSLocalizedString("Show Recent Items", comment: ""),