/
DemoStrings.swift
441 lines (381 loc) · 18.5 KB
/
DemoStrings.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
//
// DemoStrings.swift
// BonMot
//
// Created by Brian King on 8/26/16.
// Copyright © 2016 Raizlabs. All rights reserved.
//
import UIKit
import BonMot
//swiftlint:disable type_body_length file_length
enum DemoStrings {
// A Simple Example
static let simpleExample = "MY PRECIOUS"
.styled(
with:
.tracking(.point(6)),
.font(UIFont(name: "AvenirNextCondensed-Bold", size: 16)!),
.alignment(.center),
.color(BONColor(hex: 0x2769dd)),
.adapt(.control)
)
/// Style a string, with both global and range-based overrides, from an XML
/// source. This is the most common use case for styling substrings of
/// localized strings, where searching for and replacing substrings on a
/// per-language basis is cumbersome.
static let xmlExample: NSAttributedString = {
// This would typically come from NSLocalizedString
let localizedString = "I want to be different. If everyone is wearing <black><BON:noBreakSpace/>black,<BON:noBreakSpace/></black> I want to be wearing <red><BON:noBreakSpace/>red.<BON:noBreakSpace/></red>\n<signed><BON:emDash/>Maria Sharapova</signed> <racket/>"
// Define a colored image that's slightly shifted to account for the line height
let accessibleRacketImage = UIImage(named: "Tennis Racket")!
// The racket is purely for decoration, so make the accessibility system ignore it
accessibleRacketImage.accessibilityLabel = ""
let racket = accessibleRacketImage.styled(with:
.color(.raizlabsRed), .baselineOffset(-4.0))
// Define styles
let accent = StringStyle(.font(BONFont(name: "SuperClarendon-Black", size: 18)!))
let black = accent.byAdding(.color(.white), .backgroundColor(.black))
let red = accent.byAdding(.color(.white), .backgroundColor(.raizlabsRed))
let signed = accent.byAdding(.color(.raizlabsRed))
// Define the base style with xml rules for all tags
let baseStyle = StringStyle(
.font(BONFont(name: "GillSans-Light", size: 18)!),
.lineHeightMultiple(1.8),
.color(.darkGray),
.adapt(.control),
.xmlRules([
.style("black", black),
.style("red", red),
.style("signed", signed),
.enter(element: "racket", insert: racket),
]
)
)
return localizedString.styled(with: baseStyle)
}()
/// Compose a string piecewise from different components. If you are using
/// localized strings, you may not want to use this approach, since it does
/// not work well in cases where different languages have different
/// sentence structure. Instead, use XML as in the previous example. Use
/// composition only if you absolutely need to build the string from pieces.
static let compositionExample: NSAttributedString = {
// Define a colored image that's slightly shifted to account for the line height
let accessibleBoatImage = UIImage(named: "boat")!
accessibleBoatImage.accessibilityLabel = "boat"
let boat = accessibleBoatImage.styled(with:
.color(.raizlabsRed))
let baseStyle = StringStyle(
.alignment(.center),
.color(.black)
)
let preamble = baseStyle.byAdding(
.font(BONFont(name: "AvenirNext-Bold", size: 14)!),
.adapt(.body)
)
let bigger = baseStyle.byAdding(
.font(BONFont(name: "AvenirNext-Heavy", size: 64)!),
.adapt(.control)
)
return NSAttributedString.composed(of: [
"You’re going to need a\n".styled(with: preamble),
"Bigger\n".localizedUppercase.styled(with: bigger),
boat,
], baseStyle: baseStyle)
}()
static let imagesExample: NSAttributedString = {
func accessibleImage(named name: String) -> UIImage {
let image = UIImage(named: name)!
image.accessibilityLabel = name
return image
}
let bee = accessibleImage(named: "bee")
let oar = accessibleImage(named: "oar")
let knot = accessibleImage(named: "knot")
return NSAttributedString.composed(of: [
"2".styled(with: .baselineOffset(8)),
bee,
oar,
knot,
"2".styled(with: .baselineOffset(8)),
bee,
], baseStyle: StringStyle(
.font(UIFont(name: "HelveticaNeue-Bold", size: 24)!),
.adapt(.control)
))
}()
static let noBreakSpaceExample: NSAttributedString = {
let noSpaceTextStyle = StringStyle(
.font(.systemFont(ofSize: 17)),
.adapt(.control),
.color(.darkGray),
.baselineOffset(10)
)
return NSAttributedString.composed(of: [
("barn", "This"),
("bee", "string"),
("bug", "is"),
("circuit", "separated"),
("cut", "by"),
("discount", "images"),
("gift", "and"),
("pin", "no-break"),
("robot", "spaces"),
].map {
return NSAttributedString.composed(of: [
UIImage(named: $0)!,
Special.noBreakSpace,
$1.styled(with: noSpaceTextStyle),
])
}, separator: " ")
}()
/// A whimsical example using baseline offset and some math.
static let heartsExample = NSAttributedString.composed(of: (0..<20).map { i in
let offset: CGFloat = 15 * sin((CGFloat(i) / 20.0) * 7.0 * CGFloat.pi)
return "❤️".styled(with: .baselineOffset(offset))
})
static let indentationExamples: [NSAttributedString] = {
/// Using an image as a bullet.
let imageIndentation = NSAttributedString.composed(
of: [
UIImage(named: "robot")!,
Tab.headIndent(4.0),
"“It’s OK to ask for help. When doing a final exam, all the work must be yours, but in engineering, the point is to get the job done, and people are happy to help. Corollaries: You should be generous with credit, and you should be happy to help others.”",
Special.lineSeparator,
Special.emDash,
"Radia Perlman",
],
baseStyle: StringStyle(
.font(UIFont(name: "AvenirNextCondensed-Medium", size: 18.0)!),
.adapt(.control)
))
/// Using text as a bullet.
let stringIndentation = NSAttributedString.composed(
of: [
"🍑 →",
Tab.headIndent(4.0),
"You can also use strings (including emoji) for bullets, and they will still properly indent the appended text by the right amount.",
],
baseStyle: StringStyle(
.font(UIFont(name: "AvenirNextCondensed-Medium", size: 18.0)!),
.color(.darkGray),
.adapt(.control)
))
/// Parse a bulleted list out of HTML which uses <li> tags. Note the use
/// of `enter` and `exit` style rules to insert bullet characters. and
/// line breaks. As a bonus, <code> tags are formatted using a different
/// font and background color.
let xmlIndentation: NSAttributedString = {
let listItemStyle = StringStyle(
.font(UIFont(name: "AvenirNextCondensed-Medium", size: 18.0)!),
.adapt(.control),
.paragraphSpacingAfter(10.0)
)
let codeStyle = StringStyle(
.font(UIFont(name: "Menlo-Regular", size: 16.0)!),
.backgroundColor(BONColor.blue.withAlphaComponent(0.1)),
.adapt(.control)
)
let bulletString = NSAttributedString.composed(of: ["🍑 →", Tab.headIndent(4.0)])
let rules: [XMLStyleRule] = [
.style("li", listItemStyle),
.style("code", codeStyle),
.enter(element: "li", insert: bulletString),
.exit(element: "li", insert: "\n"),
]
let xml = "<li>This list is defined with XML and displayed in a single <code>UILabel</code>.</li><li>Each row is represented with an <code><li></code> tag.</li><li>Attributed strings define the string to use for bullets.</li><li>The text style is also specified for the <code><li></code> and <code><code></code> tags.</li>"
// Use this method of parsing XML if the content is not under your
// control, since you are less likley to catch edge cases while
// developing. This way, you can handle parsing errors gracefully.
guard let string = try? NSAttributedString.composed(ofXML: xml, rules: rules) else {
fatalError("Unable to load XML \(xml)")
}
return string
}()
return [
imageIndentation,
stringIndentation,
xmlIndentation,
]
}()
/// This example uses XML to combine many features, including special
/// characters and line spacing, and it uses single-character kerning to
/// move the period at the end of the sentence closer to the preceding
/// character.
static let advancedXMLAndKerningExample: NSAttributedString = {
let fullStyle = StringStyle(
.alignment(.center),
.color(.raizlabsRed),
.font(BONFont(name: "AvenirNext-Medium", size: 16)!),
.adapt(.body),
.lineSpacing(20),
.xmlRules([
.style("large", StringStyle(
.font(BONFont(name: "AvenirNext-Heavy", size: 64)!),
.lineSpacing(40),
.adapt(.control))),
.style("kern", StringStyle(
.tracking(.adobe(-80))
)),
])
)
// XML makes it hard to read. It says: "GO AHEAD, MAKE MY DAY."
let phrase = "GO<BON:noBreakSpace/>AHEAD,\n<large>MAKE\nMY\nDA<kern>Y.</kern></large>"
let attributedString = phrase.styled(with: fullStyle)
return attributedString
}()
/// Demonstrate specifying Dynamic Type sizing behavior and custom styles
/// via IBDesignable in Interface Builder. To see this example in action,
/// play with the iOS Text Size slider and see how the UI elements react.
static let dynamcTypeUIKitExample = DemoStrings.customStoryboard(identifier: "CatalogViewController")
.attributedString(from: "Dynamic UIKit elements with custom fonts")
/// Demonstrate how BonMot interacts with sytem preferred text styles.
static let preferredFontsExample = DemoStrings.customStoryboard(identifier: "PreferredFonts")
.attributedString(from: "Preferred Fonts")
// Demonstrate different number styles and spacings.
static let figureStylesExample: NSAttributedString = {
let garamondStyle = StringStyle(
.font(UIFont(name: "EBGaramond12-Regular", size: 24)!),
.lineHeightMultiple(1.2),
.adapt(.body)
)
let digits = "\n0123456789"
return NSAttributedString.composed(of: [
"Number Styles".styled(with: garamondStyle, .smallCaps(.fromLowercase), .color(.raizlabsRed)),
digits.styled(with: garamondStyle, .numberCase(.lower), .numberSpacing(.monospaced)),
digits.styled(with: garamondStyle, .numberCase(.upper), .numberSpacing(.monospaced)),
digits.styled(with: garamondStyle, .numberCase(.lower), .numberSpacing(.proportional)),
digits.styled(with: garamondStyle, .numberCase(.upper), .numberSpacing(.proportional)),
])
}()
// Demonstrate ordinals.
static let ordinalsExample: NSAttributedString = {
let garamondStyle = StringStyle(
.font(UIFont(name: "EBGaramond12-Regular", size: 24)!),
.lineHeightMultiple(1.2),
.adapt(.body)
)
let string = "Today is my <number>111<ordinal>th</ordinal></number> birthday!"
return string.styled(with: garamondStyle.byAdding(
.xmlRules([
.style("number", garamondStyle.byAdding(.color(.raizlabsRed), .numberCase(.upper))),
.style("ordinal", garamondStyle.byAdding(.ordinals(true))),
])
)
)
}()
// Demonstrate fractions.
static let fractionsExample: NSAttributedString = {
let garamondStyle = StringStyle(
.font(UIFont(name: "EBGaramond12-Regular", size: 24)!),
.numberCase(.upper),
.lineHeightMultiple(1.2),
.adapt(.body)
)
let string = "1336 <fraction>6/10</fraction> + <fraction>4/10</fraction> = 1337"
return string.styled(with: garamondStyle.byAdding(
.xmlRules([
.style("fraction", garamondStyle.byAdding(.fractions(.diagonal), .color(.raizlabsRed), .numberCase(.lower))),
])
)
)
}()
// Demonstrate scientific inferiors.
static let scientificInferiorsExample: NSAttributedString = {
let garamondStyle = StringStyle(
.font(UIFont(name: "EBGaramond12-Regular", size: 24)!),
.lineHeightMultiple(1.2),
.adapt(.body)
)
let string = "<name>Johnny</name> was a little boy, but <name>Johnny</name> is no more, for what he thought was <chemical>H<number>2</number>O</chemical> was really <chemical>H<number>2</number>SO<number>4</number></chemical>."
return string.styled(with: garamondStyle.byAdding(
.xmlRules([
.style("name", garamondStyle.byAdding(.smallCaps(.fromLowercase))),
.style("chemical", garamondStyle.byAdding(.color(.raizlabsRed))),
.style("number", garamondStyle.byAdding(.scientificInferiors(true))),
])
)
)
}()
// Demonstrate stylistic alternates.
static let stylisticAlternatesExample: NSAttributedString = {
let systemFontStyle = StringStyle(
.font(.systemFont(ofSize: 18)),
.adapt(.body)
)
let password = "68Il14"
let string = "My password, <callout>\(password)</callout>, is much easier to read with stylistic alternate set six enabled: <password>\(password)</password>."
let callout = StringStyle(.color(.raizlabsRed))
return string.styled(with: systemFontStyle, .xmlRules([
.style("callout", callout),
.style("password", callout.byAdding(.stylisticAlternates(.six(on: true)))),
])
)
}()
// Demonstrate accessibility speech attributes.
static let accessibilitySpeechExamples: [NSAttributedString] = {
let style = StringStyle(
.font(UIFont(name: "EBGaramond12-Regular", size: 24)!),
.paragraphSpacingAfter(12.0),
.adapt(.body),
.xmlRules([
.style("punctuation", StringStyle(.color(.raizlabsRed))),
.style("emoji", StringStyle(.font(.systemFont(ofSize: 24)))),
.style("speak-punctuation", StringStyle(.speaksPunctuation(true))),
.style("high-pitch", StringStyle(.speakingPitch(2.0))),
.style("low-pitch", StringStyle(.speakingPitch(0.0), .color(.raizlabsRed))),
.style("el-GR", StringStyle(.speakingLanguage("el-GR"))),
.style("en-US", StringStyle(.speakingLanguage("en-US"))),
.style("es-DO", StringStyle(.speakingLanguage("es-DO"))),
.style("fr-FR", StringStyle(.speakingLanguage("fr-FR"))),
.style("pt-PT", StringStyle(.speakingLanguage("pt-PT"))),
.style("ru-RU", StringStyle(.speakingLanguage("ru-RU"))),
.style("uk-UA", StringStyle(.speakingLanguage("uk-UA"))),
.style("zh-TW", StringStyle(.speakingLanguage("zh-TW"))),
]))
let stringsArrays = [
[
"<speak-punctuation>With VoiceOver<punctuation>,</punctuation> this string<punctuation>,</punctuation> well<punctuation>,</punctuation> reads its<punctuation>…</punctuation>punctuation<punctuation>!</punctuation></speak-punctuation>",
],
[
"<high-pitch>Helium is less dense than air, making my voice higher. </high-pitch><low-pitch>And with sulfur hexafluoride, my voice gets really low, and somehow, I’m still funny. Ha ha ha.</low-pitch>",
],
[
"<el-GR><emoji>🇬🇷</emoji> Εδώ είναι ο σωστός τρόπος για να προφέρει Ελληνικά.</el-GR>",
"<en-US>\n<emoji>🇺🇸</emoji> Here is the correct way to pronounce American English.</en-US>",
"<es-DO>\n<emoji>🇩🇴</emoji> Aquí está la manera correcta de pronunciar el español.</es-DO>",
"<fr-FR>\n<emoji>🇫🇷</emoji> Voici comment prononcer correctement le français.</fr-FR>",
"<pt-PT>\n<emoji>🇵🇹</emoji> Aqui está a maneira correta de pronunciar português.</pt-PT>",
"<ru-RU>\n<emoji>🇷🇺</emoji> Вот как правильно произносить русский.</ru-RU>",
"<uk-UA>\n<emoji>🇺🇦</emoji> Ось як правильно вимовляти Український.</uk-UA>",
"<zh-TW>\n<emoji>🇹🇼</emoji> 这里是正确的做法发音中文.</zh-TW>",
],
]
#if swift(>=3.0)
return stringsArrays.map { $0.joined().styled(with: style) }
#else
return stringsArrays.map { $0.joinWithSeparator("").styled(with: style) }
#endif
}()
}
//swiftlint:enable type_body_length
extension DemoStrings {
/// Embed an attribute for the storyboard identifier to link to. This is
/// a good example of embedding custom attributes in an attributed string,
/// although it might not be the best UIKit design pattern.
///
/// - Parameter theIdentifier: The identifier of the storyboard in question.
/// - Returns: A string style that contains the extra storyboard attribute.
static func customStoryboard(identifier theIdentifier: String) -> StringStyle {
return StringStyle(.extraAttributes(["Storyboard": theIdentifier]))
}
}
#if swift(>=3.0)
#else
extension CGFloat {
static var pi: CGFloat {
//swiftlint:disable:next legacy_constant
return CGFloat(M_PI)
}
}
#endif
//swiftlint:enable file_length