@@ -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
+ public 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,39 @@ 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
+ guard let obj = obj else {
239
+ return nil
240
+ }
241
+
242
+ var modifyAttributedValueByKey : ( ( TokenType , NSAttributedString ) -> NSAttributedString ) ?
243
+ if let modifyValueByKey = modifyValueByKey {
244
+ modifyAttributedValueByKey = { ( key: TokenType , value: NSAttributedString ) -> NSAttributedString in
245
+ return NSAttributedString ( string: modifyValueByKey ( key, value. string) )
246
+ }
247
+ }
248
+ return attributedString ( for: obj, legIndex: legIndex, numberOfLegs: numberOfLegs, roadClasses: roadClasses, modifyValueByKey: modifyAttributedValueByKey) ? . string
249
+ }
250
+
251
+ /**
252
+ Creates an instruction as an attributed string given a step and options.
253
+
254
+ - parameter obj: The step to format.
255
+ - parameter attrs: The default attributes to use for the returned attributed string.
256
+ - parameter legIndex: Current leg index the user is currently on.
257
+ - parameter numberOfLegs: Total number of `RouteLeg` for the given `Route`.
258
+ - parameter roadClasses: Option set representing the classes of road for the `RouteStep`.
259
+ - parameter modifyValueByKey: Allows for mutating the instruction at given parts of the instruction.
260
+ - returns: An instruction as an `NSAttributedString`.
261
+ */
262
+ public func attributedString( for obj: Any , withDefaultAttributes attrs: [ String : Any ] ? = nil , legIndex: Int ? , numberOfLegs: Int ? , roadClasses: RoadClasses ? = RoadClasses ( [ ] ) , modifyValueByKey: ( ( TokenType , NSAttributedString ) -> NSAttributedString ) ? ) -> NSAttributedString ? {
190
263
guard let step = obj as? RouteStep else {
191
264
return nil
192
265
}
@@ -208,14 +281,14 @@ public class OSRMInstructionFormatter: Formatter {
208
281
209
282
var instructionObject : InstructionsByToken
210
283
var rotaryName = " "
211
- var wayName : String
284
+ var wayName : NSAttributedString
212
285
switch type {
213
286
case . takeRotary, . takeRoundabout:
214
287
// Special instruction types have an intermediate level keyed to “default”.
215
288
let instructionsByModifier = instructions [ type. description] as! [ String : InstructionsByModifier ]
216
289
let defaultInstructions = instructionsByModifier [ " default " ] !
217
290
218
- wayName = step. exitNames? . first ?? " "
291
+ wayName = NSAttributedString ( string : step. exitNames? . first ?? " " , attributes : attrs )
219
292
if let _rotaryName = step. names? . first, let _ = step. exitIndex, let obj = defaultInstructions [ " name_exit " ] {
220
293
instructionObject = obj
221
294
rotaryName = _rotaryName
@@ -244,22 +317,42 @@ public class OSRMInstructionFormatter: Formatter {
244
317
let isMotorway = roadClasses? . contains ( . motorway) ?? false
245
318
246
319
if let name = name, let ref = ref, name != ref, !isMotorway {
247
- wayName = phrase ( named: . nameWithCode) . replacingTokens ( using: { ( tokenType) -> String in
320
+ let attributedName = NSAttributedString ( string: name, attributes: attrs)
321
+ let attributedRef = NSAttributedString ( string: ref, attributes: attrs)
322
+ let phrase = NSAttributedString ( string: self . phrase ( named: . nameWithCode) , attributes: attrs)
323
+ wayName = phrase. replacingTokens ( using: { ( tokenType) -> NSAttributedString in
248
324
switch tokenType {
249
325
case . wayName:
250
- return modifyValueByKey ? ( . wayName, name ) ?? name
326
+ return modifyValueByKey ? ( . wayName, attributedName ) ?? attributedName
251
327
case . code:
252
- return modifyValueByKey ? ( . code, ref ) ?? ref
328
+ return modifyValueByKey ? ( . code, attributedRef ) ?? attributedRef
253
329
default :
254
330
fatalError ( " Unexpected token type \( tokenType) in name-and-ref phrase " )
255
331
}
256
332
} )
257
333
} else if let ref = ref, isMotorway, let decimalRange = ref. rangeOfCharacter ( from: . decimalDigits) , !decimalRange. isEmpty {
258
- wayName = modifyValueByKey != nil ? " \( modifyValueByKey!( . code, ref) ) " : ref
334
+ let attributedRef = NSAttributedString ( string: ref, attributes: attrs)
335
+ if let modifyValueByKey = modifyValueByKey {
336
+ wayName = modifyValueByKey ( . code, attributedRef)
337
+ } else {
338
+ wayName = attributedRef
339
+ }
259
340
} else if name == nil , let ref = ref {
260
- wayName = modifyValueByKey != nil ? " \( modifyValueByKey!( . code, ref) ) " : ref
341
+ let attributedRef = NSAttributedString ( string: ref, attributes: attrs)
342
+ if let modifyValueByKey = modifyValueByKey {
343
+ wayName = modifyValueByKey ( . code, attributedRef)
344
+ } else {
345
+ wayName = attributedRef
346
+ }
347
+ } else if let name = name {
348
+ let attributedName = NSAttributedString ( string: name, attributes: attrs)
349
+ if let modifyValueByKey = modifyValueByKey {
350
+ wayName = modifyValueByKey ( . wayName, attributedName)
351
+ } else {
352
+ wayName = attributedName
353
+ }
261
354
} else {
262
- wayName = name != nil ? modifyValueByKey != nil ? " \( modifyValueByKey! ( . wayName , name! ) ) " : name! : " "
355
+ wayName = NSAttributedString ( )
263
356
}
264
357
}
265
358
@@ -292,7 +385,7 @@ public class OSRMInstructionFormatter: Formatter {
292
385
instruction = obj
293
386
} else if let _ = step. exitCodes? . first, let obj = instructionObject [ " exit " ] {
294
387
instruction = obj
295
- } else if !wayName. isEmpty, let obj = instructionObject [ " name " ] {
388
+ } else if !wayName. string . isEmpty, let obj = instructionObject [ " name " ] {
296
389
instruction = obj
297
390
} else {
298
391
instruction = instructionObject [ " default " ] !
@@ -315,11 +408,11 @@ public class OSRMInstructionFormatter: Formatter {
315
408
if step. finalHeading != nil { bearing = Int ( step. finalHeading! as Double ) }
316
409
317
410
// Replace tokens
318
- let result = instruction. replacingTokens { ( tokenType) -> String in
411
+ let result = NSAttributedString ( string : instruction, attributes : attrs ) . replacingTokens { ( tokenType) -> NSAttributedString in
319
412
var replacement : String
320
413
switch tokenType {
321
414
case . code: replacement = step. codes? . first ?? " "
322
- case . wayName: replacement = wayName
415
+ case . wayName: replacement = " " // ignored
323
416
case . destination: replacement = destination
324
417
case . exitCode: replacement = exitCode
325
418
case . exitIndex: replacement = exitOrdinal
@@ -332,9 +425,10 @@ public class OSRMInstructionFormatter: Formatter {
332
425
fatalError ( " Unexpected token type \( tokenType) in individual instruction " )
333
426
}
334
427
if tokenType == . wayName {
335
- return replacement // already modified above
428
+ return wayName // already modified above
336
429
} else {
337
- return modifyValueByKey ? ( tokenType, replacement) ?? replacement
430
+ let attributedReplacement = NSAttributedString ( string: replacement, attributes: attrs)
431
+ return modifyValueByKey ? ( tokenType, attributedReplacement) ?? attributedReplacement
338
432
}
339
433
}
340
434
0 commit comments