@@ -4,14 +4,20 @@ import MapboxDirections
4
4
// Will automatically read localized Instructions.plist
5
5
let OSRMTextInstructionsStrings = NSDictionary ( contentsOfFile: Bundle ( for: OSRMInstructionFormatter . self) . path ( forResource: " Instructions " , ofType: " plist " ) !) !
6
6
7
- extension String {
8
- public var sentenceCased : String {
9
- return String ( characters. prefix ( 1 ) ) . uppercased ( ) + String( characters. dropFirst ( ) )
10
- }
7
+ protocol Tokenized {
8
+ associatedtype T
11
9
12
10
/**
13
11
Replaces `{tokens}` in the receiver using the given closure.
14
12
*/
13
+ func replacingTokens( using interpolator: ( ( TokenType ) -> T ) ) -> T
14
+ }
15
+
16
+ extension String : Tokenized {
17
+ public var sentenceCased : String {
18
+ return String ( characters. prefix ( 1 ) ) . uppercased ( ) + String( characters. dropFirst ( ) )
19
+ }
20
+
15
21
public func replacingTokens( using interpolator: ( ( TokenType ) -> String ) ) -> String {
16
22
let scanner = Scanner ( string: self )
17
23
scanner. charactersToBeSkipped = nil
@@ -52,6 +58,48 @@ extension String {
52
58
}
53
59
}
54
60
61
+ extension NSAttributedString : Tokenized {
62
+ func replacingTokens( using interpolator: ( ( TokenType ) -> NSAttributedString ) ) -> NSAttributedString {
63
+ let scanner = Scanner ( string: string)
64
+ scanner. charactersToBeSkipped = nil
65
+ let result = NSMutableAttributedString ( )
66
+ while !scanner. isAtEnd {
67
+ var buffer : NSString ?
68
+
69
+ if scanner. scanUpTo ( " { " , into: & buffer) {
70
+ result. append ( NSAttributedString ( string: buffer! as String ) )
71
+ }
72
+ guard scanner. scanString ( " { " , into: nil ) else {
73
+ continue
74
+ }
75
+
76
+ var token : NSString ?
77
+ guard scanner. scanUpTo ( " } " , into: & token) else {
78
+ continue
79
+ }
80
+
81
+ if scanner. scanString ( " } " , into: nil ) {
82
+ if let tokenType = TokenType ( description: token! as String ) {
83
+ result. append ( interpolator ( tokenType) )
84
+ }
85
+ } else {
86
+ result. append ( NSAttributedString ( string: token! as String ) )
87
+ }
88
+ }
89
+
90
+ // remove excess spaces
91
+ let wholeRange = NSRange ( location: 0 , length: result. mutableString. length)
92
+ result. mutableString. replaceOccurrences ( of: " \\ s \\ s " , with: " " , options: . regularExpression, range: wholeRange)
93
+
94
+ // capitalize
95
+ let meta = OSRMTextInstructionsStrings [ " meta " ] as! [ String : Any ]
96
+ if meta [ " capitalizeFirstLetter " ] as? Bool ?? false {
97
+ result. replaceCharacters ( in: NSRange ( location: 0 , length: 1 ) , with: String ( result. string. characters. first!) . uppercased ( ) )
98
+ }
99
+ return result as NSAttributedString
100
+ }
101
+ }
102
+
55
103
public class OSRMInstructionFormatter : Formatter {
56
104
let version : String
57
105
let instructions : [ String : Any ]
@@ -179,14 +227,34 @@ public class OSRMInstructionFormatter: Formatter {
179
227
/**
180
228
Creates an instruction given a step and options.
181
229
182
- - parameter step:
230
+ - parameter step: The step to format.
183
231
- parameter legIndex: Current leg index the user is currently on.
184
232
- parameter numberOfLegs: Total number of `RouteLeg` for the given `Route`.
185
233
- parameter roadClasses: Option set representing the classes of road for the `RouteStep`.
186
234
- parameter modifyValueByKey: Allows for mutating the instruction at given parts of the instruction.
187
235
- returns: An instruction as a `String`.
188
236
*/
189
237
public func string( for obj: Any ? , legIndex: Int ? , numberOfLegs: Int ? , roadClasses: RoadClasses ? = RoadClasses ( [ ] ) , modifyValueByKey: ( ( TokenType , String ) -> String ) ? ) -> String ? {
238
+ var modifyAttributedValueByKey : ( ( TokenType , NSAttributedString ) -> NSAttributedString ) ?
239
+ if let modifyValueByKey = modifyValueByKey {
240
+ modifyAttributedValueByKey = { ( key: TokenType , value: NSAttributedString ) -> NSAttributedString in
241
+ return NSAttributedString ( string: modifyValueByKey ( key, value. string) )
242
+ }
243
+ }
244
+ return attributedString ( for: obj, legIndex: legIndex, numberOfLegs: numberOfLegs, roadClasses: roadClasses, modifyValueByKey: modifyAttributedValueByKey) ? . string
245
+ }
246
+
247
+ /**
248
+ Creates an instruction as an attributed string given a step and options.
249
+
250
+ - parameter step: The step to format.
251
+ - parameter legIndex: Current leg index the user is currently on.
252
+ - parameter numberOfLegs: Total number of `RouteLeg` for the given `Route`.
253
+ - parameter roadClasses: Option set representing the classes of road for the `RouteStep`.
254
+ - parameter modifyValueByKey: Allows for mutating the instruction at given parts of the instruction.
255
+ - returns: An instruction as an `NSAttributedString`.
256
+ */
257
+ public func attributedString( for obj: Any ? , legIndex: Int ? , numberOfLegs: Int ? , roadClasses: RoadClasses ? = RoadClasses ( [ ] ) , modifyValueByKey: ( ( TokenType , NSAttributedString ) -> NSAttributedString ) ? ) -> NSAttributedString ? {
190
258
guard let step = obj as? RouteStep else {
191
259
return nil
192
260
}
@@ -208,14 +276,14 @@ public class OSRMInstructionFormatter: Formatter {
208
276
209
277
var instructionObject : InstructionsByToken
210
278
var rotaryName = " "
211
- var wayName : String
279
+ var wayName : NSAttributedString
212
280
switch type {
213
281
case . takeRotary, . takeRoundabout:
214
282
// Special instruction types have an intermediate level keyed to “default”.
215
283
let instructionsByModifier = instructions [ type. description] as! [ String : InstructionsByModifier ]
216
284
let defaultInstructions = instructionsByModifier [ " default " ] !
217
285
218
- wayName = step. exitNames? . first ?? " "
286
+ wayName = NSAttributedString ( string : step. exitNames? . first ?? " " )
219
287
if let _rotaryName = step. names? . first, let _ = step. exitIndex, let obj = defaultInstructions [ " name_exit " ] {
220
288
instructionObject = obj
221
289
rotaryName = _rotaryName
@@ -244,22 +312,42 @@ public class OSRMInstructionFormatter: Formatter {
244
312
let isMotorway = roadClasses? . contains ( . motorway) ?? false
245
313
246
314
if let name = name, let ref = ref, name != ref, !isMotorway {
247
- wayName = phrase ( named: . nameWithCode) . replacingTokens ( using: { ( tokenType) -> String in
315
+ let attributedName = NSAttributedString ( string: name)
316
+ let attributedRef = NSAttributedString ( string: ref)
317
+ let phrase = NSAttributedString ( string: self . phrase ( named: . nameWithCode) )
318
+ wayName = phrase. replacingTokens ( using: { ( tokenType) -> NSAttributedString in
248
319
switch tokenType {
249
320
case . wayName:
250
- return modifyValueByKey ? ( . wayName, name ) ?? name
321
+ return modifyValueByKey ? ( . wayName, attributedName ) ?? attributedName
251
322
case . code:
252
- return modifyValueByKey ? ( . code, ref ) ?? ref
323
+ return modifyValueByKey ? ( . code, attributedRef ) ?? attributedRef
253
324
default :
254
325
fatalError ( " Unexpected token type \( tokenType) in name-and-ref phrase " )
255
326
}
256
327
} )
257
328
} else if let ref = ref, isMotorway, let decimalRange = ref. rangeOfCharacter ( from: . decimalDigits) , !decimalRange. isEmpty {
258
- wayName = modifyValueByKey != nil ? " \( modifyValueByKey!( . code, ref) ) " : ref
329
+ let attributedRef = NSAttributedString ( string: ref)
330
+ if let modifyValueByKey = modifyValueByKey {
331
+ wayName = modifyValueByKey ( . code, attributedRef)
332
+ } else {
333
+ wayName = attributedRef
334
+ }
259
335
} else if name == nil , let ref = ref {
260
- wayName = modifyValueByKey != nil ? " \( modifyValueByKey!( . code, ref) ) " : ref
336
+ let attributedRef = NSAttributedString ( string: ref)
337
+ if let modifyValueByKey = modifyValueByKey {
338
+ wayName = modifyValueByKey ( . code, attributedRef)
339
+ } else {
340
+ wayName = attributedRef
341
+ }
342
+ } else if let name = name {
343
+ let attributedName = NSAttributedString ( string: name)
344
+ if let modifyValueByKey = modifyValueByKey {
345
+ wayName = modifyValueByKey ( . wayName, attributedName)
346
+ } else {
347
+ wayName = attributedName
348
+ }
261
349
} else {
262
- wayName = name != nil ? modifyValueByKey != nil ? " \( modifyValueByKey! ( . wayName , name! ) ) " : name! : " "
350
+ wayName = NSAttributedString ( )
263
351
}
264
352
}
265
353
@@ -292,7 +380,7 @@ public class OSRMInstructionFormatter: Formatter {
292
380
instruction = obj
293
381
} else if let _ = step. exitCodes? . first, let obj = instructionObject [ " exit " ] {
294
382
instruction = obj
295
- } else if !wayName. isEmpty, let obj = instructionObject [ " name " ] {
383
+ } else if !wayName. string . isEmpty, let obj = instructionObject [ " name " ] {
296
384
instruction = obj
297
385
} else {
298
386
instruction = instructionObject [ " default " ] !
@@ -315,11 +403,11 @@ public class OSRMInstructionFormatter: Formatter {
315
403
if step. finalHeading != nil { bearing = Int ( step. finalHeading! as Double ) }
316
404
317
405
// Replace tokens
318
- let result = instruction. replacingTokens { ( tokenType) -> String in
406
+ let result = NSAttributedString ( string : instruction) . replacingTokens { ( tokenType) -> NSAttributedString in
319
407
var replacement : String
320
408
switch tokenType {
321
409
case . code: replacement = step. codes? . first ?? " "
322
- case . wayName: replacement = wayName
410
+ case . wayName: replacement = " " // ignored
323
411
case . destination: replacement = destination
324
412
case . exitCode: replacement = exitCode
325
413
case . exitIndex: replacement = exitOrdinal
@@ -332,9 +420,10 @@ public class OSRMInstructionFormatter: Formatter {
332
420
fatalError ( " Unexpected token type \( tokenType) in individual instruction " )
333
421
}
334
422
if tokenType == . wayName {
335
- return replacement // already modified above
423
+ return wayName // already modified above
336
424
} else {
337
- return modifyValueByKey ? ( tokenType, replacement) ?? replacement
425
+ let attributedReplacement = NSAttributedString ( string: replacement)
426
+ return modifyValueByKey ? ( tokenType, attributedReplacement) ?? attributedReplacement
338
427
}
339
428
}
340
429
0 commit comments