Skip to content

Commit 8a04680

Browse files
authored
Fix occasional crash when typing ñ into search box (#391)
1 parent 956d901 commit 8a04680

File tree

5 files changed

+58
-16
lines changed

5 files changed

+58
-16
lines changed

Sources/TripKit/search/TKAutocompletionResult+Score.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,16 @@ extension TKAutocompletionResult {
8181
return 100 // having typed yet means a perfect match of everything you've typed so far
8282
}
8383

84+
85+
func nsRange(of string: String) -> NSRange? {
86+
let haystack = fullCandidate.lowercased()
87+
let needle = string.lowercased()
88+
let range = (haystack as NSString).range(of: needle)
89+
return range.location == NSNotFound ? nil : range
90+
}
91+
8492
if target == candidate {
85-
return .init(score: 100, ranges: [.init(location: 0, length: candidate.utf8.count)])
93+
return .init(score: 100, ranges: [nsRange(of: candidate)].compactMap { $0 })
8694
}
8795

8896
if target.isAbbreviation(for: candidate) || target.isAbbreviation(for: stringForScoring(fullCandidate, removeBrackets: true)) {
@@ -92,13 +100,7 @@ extension TKAutocompletionResult {
92100
if candidate.isAbbreviation(for: target) || candidate.isAbbreviation(for: stringForScoring(fullTarget, removeBrackets: true)) {
93101
return 90
94102
}
95-
96-
func nsRange(of string: String) -> NSRange? {
97-
let haystack = fullCandidate.lowercased()
98-
let needle = string.lowercased()
99-
let range = (haystack as NSString).range(of: needle)
100-
return range.location == NSNotFound ? nil : range
101-
}
103+
102104

103105
// exact phrase matches
104106
let excess = candidate.utf8.count - target.utf8.count

Sources/TripKit/search/TKAutocompletionResult.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,19 @@ public struct TKAutocompletionResult {
2828

2929
public let title: String
3030

31-
public var titleHighlightRanges: [NSRange] = []
31+
public var titleHighlightRanges: [NSRange] = [] {
32+
didSet {
33+
assert(titleHighlightRanges.allSatisfy({ title.count >= $0.upperBound }))
34+
}
35+
}
3236

3337
public var subtitle: String? = nil
3438

35-
public var subtitleHighlightRanges: [NSRange] = []
39+
public var subtitleHighlightRanges: [NSRange] = [] {
40+
didSet {
41+
assert(titleHighlightRanges.allSatisfy({ (subtitle ?? "").count >= $0.upperBound }))
42+
}
43+
}
3644

3745
public let image: TKImage
3846

Sources/TripKit/search/TKGeocodingResultScorer.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,9 @@
1111
import Foundation
1212
import MapKit
1313

14-
public class TKGeocodingResultScorer: NSObject {
14+
enum TKGeocodingResultScorer {
1515

16-
private override init() {
17-
}
18-
19-
public static func calculateScore(for annotation: MKAnnotation, searchTerm: String, near region: MKCoordinateRegion, allowLongDistance: Bool, minimum: Int, maximum: Int) -> TKAutocompletionResult.ScoreHighlights {
16+
static func calculateScore(for annotation: MKAnnotation, searchTerm: String, near region: MKCoordinateRegion, allowLongDistance: Bool, minimum: Int, maximum: Int) -> TKAutocompletionResult.ScoreHighlights {
2017

2118
guard let title = (annotation.title ?? nil) else {
2219
return .init(score: 0)
@@ -42,7 +39,7 @@ public class TKGeocodingResultScorer: NSObject {
4239
)
4340
}
4441

45-
public static func calculateScore(title: String, subtitle: String?, searchTerm: String, minimum: Int, maximum: Int) -> TKAutocompletionResult.ScoreHighlights {
42+
static func calculateScore(title: String, subtitle: String?, searchTerm: String, minimum: Int, maximum: Int) -> TKAutocompletionResult.ScoreHighlights {
4643
assert(maximum > minimum, "Order must be preserved.")
4744

4845
let titleScore = TKAutocompletionResult.nameScore(searchTerm: searchTerm, candidate: title)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// TKAutocompletionHighlightTest.swift
3+
// TripKitTests
4+
//
5+
// Created by Adrian Schönig on 7/3/2025.
6+
// Copyright © 2025 SkedGo Pty Ltd. All rights reserved.
7+
//
8+
9+
#if canImport(Testing)
10+
11+
import Testing
12+
13+
@testable import TripKit
14+
15+
struct TKAutocompletionHighlightTest {
16+
17+
@Test func spanishHighlights() async throws {
18+
19+
let highlights = TKAutocompletionResult.nameScore(
20+
searchTerm: "Hotel España",
21+
candidate: "Hotel españa"
22+
)
23+
24+
#expect(highlights.ranges.count == 1)
25+
#expect(highlights.ranges.first?.location == 0)
26+
#expect(highlights.ranges.first?.length == 12)
27+
}
28+
29+
}
30+
31+
#endif

TripKit.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
3A0F254C27F5397100DEB6A0 /* NSManagedObject+Safely.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0F254B27F5397100DEB6A0 /* NSManagedObject+Safely.swift */; };
11+
3A10C71A2D7AA1A10079C015 /* TKAutocompletionHighlightTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A10C7192D7AA1A10079C015 /* TKAutocompletionHighlightTest.swift */; };
1112
3A1777AC232C17560093D495 /* TripKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AB6C6201D1CD0060089F687 /* TripKit.framework */; };
1213
3A1777AD232C17560093D495 /* TripKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AB6C6201D1CD0060089F687 /* TripKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1314
3A1777AF232C17570093D495 /* TripKitInterApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A6DF167202C97F60084CB2C /* TripKitInterApp.framework */; };
@@ -767,6 +768,7 @@
767768

768769
/* Begin PBXFileReference section */
769770
3A0F254B27F5397100DEB6A0 /* NSManagedObject+Safely.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Safely.swift"; sourceTree = "<group>"; };
771+
3A10C7192D7AA1A10079C015 /* TKAutocompletionHighlightTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TKAutocompletionHighlightTest.swift; sourceTree = "<group>"; };
770772
3A19BE2427ABA44500B4AA8C /* TKUITripTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TKUITripTitleView.swift; sourceTree = "<group>"; };
771773
3A19BE2627ABA44D00B4AA8C /* TKUITripTitleView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TKUITripTitleView.xib; sourceTree = "<group>"; };
772774
3A1AABAA26CB6F8000FDDB06 /* TKAutocompletionResult+Icon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TKAutocompletionResult+Icon.swift"; sourceTree = "<group>"; };
@@ -2683,6 +2685,7 @@
26832685
isa = PBXGroup;
26842686
children = (
26852687
3AFF2A1D26C69A96007809CD /* TKAnnotationClusterer.swift */,
2688+
3A10C7192D7AA1A10079C015 /* TKAutocompletionHighlightTest.swift */,
26862689
3AD653EF28FF544F00A846C5 /* TKAutocompletionResultTest.swift */,
26872690
3AFF2A1C26C69A96007809CD /* TKGeoJSONTest.swift */,
26882691
3AFF2A1B26C69A96007809CD /* TKPeliasTitleTest.swift */,
@@ -3630,6 +3633,7 @@
36303633
3ACD91E826CCC47B0075CCF3 /* TKUIPolylineRendererStabilityTest.swift in Sources */,
36313634
3ACD920026CCC47B0075CCF3 /* ObservableConvertibleType+Blocking.swift in Sources */,
36323635
3ACD91FF26CCC47B0075CCF3 /* BlockingObservable.swift in Sources */,
3636+
3A10C71A2D7AA1A10079C015 /* TKAutocompletionHighlightTest.swift in Sources */,
36333637
3ACD91EF26CCC47B0075CCF3 /* Event+Equatable.swift in Sources */,
36343638
3ACD91ED26CCC47B0075CCF3 /* Any+Equatable.swift in Sources */,
36353639
3AFF2AA426C69A96007809CD /* TKBuzzInfoProviderTest.swift in Sources */,

0 commit comments

Comments
 (0)