Skip to content

Commit 600e50d

Browse files
committed
Merge branch 'master' into develop
2 parents 654c2b2 + d60f117 commit 600e50d

File tree

5 files changed

+133
-62
lines changed

5 files changed

+133
-62
lines changed

README.md

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ It provides convenient way to store styles you can reuse in your app's UI elemen
1414
| | Features Highlights |
1515
|--- |--------------------------------------------------------------------------------- |
1616
| 🦄 | Easy styling and typography managment with coincise declarative syntax |
17-
| 🏞 | Attach local and remote images inside text |
17+
| 🏞 | Attach local images (lazy/static) and remote images inside text |
1818
| 🧬 | Fast & high customizable XML/HTML tagged string rendering |
1919
| 🌟 | Apply text transforms within styles |
2020
| 📐 | Native support for iOS 11 Dynamic Type |
@@ -35,7 +35,7 @@ let attributedText = "Hello World!".set(style: style) // et voilà!
3535
```
3636

3737
### XML/HTML tag based rendering
38-
SwiftRichString allows you to render complex strings by parsing text's tags: each style will be identified by an unique name (used inside the tag) and you can create a `StyleGroup` which allows you to encapsulate all of them and reuse as you need (clearly you can register it globally).
38+
SwiftRichString allows you to render complex strings by parsing text's tags: each style will be identified by an unique name (used inside the tag) and you can create a `StyleXML` (was `StyleGroup`) which allows you to encapsulate all of them and reuse as you need (clearly you can register it globally).
3939

4040
```swift
4141
// Create your own styles
@@ -54,7 +54,7 @@ let italic = normal.byAdding {
5454
$0.traitVariants = .italic
5555
}
5656

57-
let myGroup = StyleGroup(base: normal, ["bold": bold, "italic": italic])
57+
let myGroup = StyleXML(base: normal, ["bold": bold, "italic": italic])
5858
let str = "Hello <bold>Daniele!</bold>. You're ready to <italic>play with us!</italic>"
5959
self.label?.attributedText = str.set(style: myGroup)
6060
```
@@ -65,7 +65,7 @@ That's the result!
6565

6666
## Documentation
6767

68-
- [Introduction to `Style`, `StyleGroup` & `StyleRegEx`](#stylestylegroup)
68+
- [Introduction to `Style`, `StyleXML` & `StyleRegEx`](#styleStyleXML)
6969
- [String & Attributed String concatenation](#concatenation)
7070
- [Apply styles to `String` & `Attributed String`](#manualstyling)
7171
- [Fonts & Colors in `Style`](#fontscolors)
@@ -88,12 +88,12 @@ Other info:
8888
- [Contributing](#contributing)
8989
- [Copyright](#copyright)
9090

91-
<a name="stylestylegroup"/>
91+
<a name="styleStyleXML"/>
9292

93-
## Introduction to `Style`, `StyleGroup`, `StyleRegEx`
93+
## Introduction to `Style`, `StyleXML`, `StyleRegEx`
9494

9595
The main concept behind SwiftRichString is the use of `StyleProtocol` as generic container of the attributes you can apply to both `String` and `NSMutableAttributedString`.
96-
Concrete classes derivated by `StyleProtocol` are: `Style`, `StyleGroup` and `StyleRegEx`.
96+
Concrete classes derivated by `StyleProtocol` are: `Style`, `StyleXML` and `StyleRegEx`.
9797

9898
Each of these classes can be used as source for styles you can apply to a string, substring or attributed string.
9999

@@ -114,15 +114,15 @@ let style = Style {
114114
let attrString = "Some text".set(style: style) // attributed string
115115
```
116116

117-
### `StyleGroup`: Apply styles for tag-based complex string
117+
### `StyleXML`: Apply styles for tag-based complex string
118118

119-
`Style` instances are anonymous; if you want to use a style instance to render a tag-based plain string you need to include it inside a `StyleGroup`. You can consider a `StyleGroup` as a container of `Styles` (but, in fact, thanks to the conformance to a common `StyleProtocol`'s protocol your group may contains other sub-groups too).
119+
`Style` instances are anonymous; if you want to use a style instance to render a tag-based plain string you need to include it inside a `StyleXML`. You can consider a `StyleXML` as a container of `Styles` (but, in fact, thanks to the conformance to a common `StyleProtocol`'s protocol your group may contains other sub-groups too).
120120

121121
```swift
122122
let bodyStyle: Style = ...
123123
let h1Style: Style = ...
124124
let h2Style: Style = ...
125-
let group = StyleGroup(base: bodyStyle, ["h1": h1Style, "h2": h2Style])
125+
let group = StyleXML(base: bodyStyle, ["h1": h1Style, "h2": h2Style])
126126

127127
let attrString = "Some <h1>text</h1>, <h2>welcome here</h2>".set(style: group)
128128
```
@@ -200,8 +200,8 @@ Both `String` and `Attributed String` (aka `NSMutableAttributedString`) has a co
200200

201201
- `set(style: String, range: NSRange? = nil)`: apply a globally registered style to the string (or a substring) by producing an attributed string.
202202
- `set(styles: [String], range: NSRange? = nil)`: apply an ordered sequence of globally registered styles to the string (or a substring) by producing an attributed string.
203-
- `set(style: StyleProtocol, range: NSRange? = nil)`: apply an instance of `Style` or `StyleGroup` (to render tag-based text) to the string (or a substring) by producting an attributed string.
204-
- `set(styles: [StyleProtocol], range: NSRange? = nil)`: apply a sequence of `Style`/`StyleGroup` instance in order to produce a single attributes collection which will be applied to the string (or substring) to produce an attributed string.
203+
- `set(style: StyleProtocol, range: NSRange? = nil)`: apply an instance of `Style` or `StyleXML` (to render tag-based text) to the string (or a substring) by producting an attributed string.
204+
- `set(styles: [StyleProtocol], range: NSRange? = nil)`: apply a sequence of `Style`/`StyleXML` instance in order to produce a single attributes collection which will be applied to the string (or substring) to produce an attributed string.
205205

206206
Some examples:
207207

@@ -214,8 +214,8 @@ let a1: AttributedString = "Hello world".set(style: "MyStyle")
214214
// styleH1 and styleH2 will be applied only for text inside that tags.
215215
let styleH1: Style = ...
216216
let styleH2: Style = ...
217-
let styleGroup = StyleGroup(base: commonStyle, ["h1" : styleH1, "h2" : styleH2])
218-
let a2: AttributedString = "Hello <h1>world</h1>, <h2>welcome here</h2>".set(style: styleGroup)
217+
let StyleXML = StyleXML(base: commonStyle, ["h1" : styleH1, "h2" : styleH2])
218+
let a2: AttributedString = "Hello <h1>world</h1>, <h2>welcome here</h2>".set(style: StyleXML)
219219

220220
// Apply a style defined via closure to a portion of the string
221221
let a3 = "Hello Guys!".set(Style({ $0.font = SystemFonts.Helvetica_Bold.font(size: 20) }), range: NSMakeRange(0,4))
@@ -344,7 +344,7 @@ let style = Style {
344344

345345
SwiftRichString is also able to parse and render xml tagged strings to produce a valid `NSAttributedString` instance. This is particularly useful when you receive dynamic strings from remote services and you need to produce a rendered string easily.
346346

347-
In order to render an XML string you need to create a compisition of all styles you are planning to render in a single `StyleGroup` instance and apply it to your source string as just you made for a single `Style`.
347+
In order to render an XML string you need to create a compisition of all styles you are planning to render in a single `StyleXML` instance and apply it to your source string as just you made for a single `Style`.
348348

349349
For example:
350350

@@ -370,7 +370,7 @@ let italicStyle = Style {
370370
}
371371

372372
// A group container includes all the style defined.
373-
let groupStyle = StyleGroup.init(base: baseStyle, ["b" : boldStyle, "i": italicStyle])
373+
let groupStyle = StyleXML.init(base: baseStyle, ["b" : boldStyle, "i": italicStyle])
374374

375375
// We can render our string
376376
let bodyHTML = "Hello <b>world!</b>, my name is <i>Daniele</i>"
@@ -381,12 +381,12 @@ self.textView?.attributedText = bodyHTML.set(style: group)
381381

382382
## Customize XML rendering: react to tag's attributes and unknown tags
383383

384-
You can also add custom attributes to your tags and render it as you prefer: you need to provide a croncrete implementation of `XMLDynamicAttributesResolver` protocol and assign it to the `StyleGroup`'s `.xmlAttributesResolver` property.
384+
You can also add custom attributes to your tags and render it as you prefer: you need to provide a croncrete implementation of `XMLDynamicAttributesResolver` protocol and assign it to the `StyleXML`'s `.xmlAttributesResolver` property.
385385

386386
The protocol will receive two kind of events:
387387

388388
- `applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle)` is received when parser encounter an existing style with custom attributes. Style is applied and event is called so you can make further customizations.
389-
- `func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String: String]?)` is received when a unknown (not defined in `StyleGroup`'s styles) tag is received. You can decide to ignore or perform customizations.
389+
- `func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String: String]?)` is received when a unknown (not defined in `StyleXML`'s styles) tag is received. You can decide to ignore or perform customizations.
390390

391391
The following example is used to override text color for when used for any known tag:
392392

@@ -410,8 +410,8 @@ open class MyXMLDynamicAttributesResolver: XMLDynamicAttributesResolver {
410410
}
411411
}
412412

413-
// Then set it to our's StyleGroup instance before rendering text.
414-
let groupStyle = StyleGroup.init(base: baseStyle, ["b" : boldStyle, "i": italicStyle])
413+
// Then set it to our's StyleXML instance before rendering text.
414+
let groupStyle = StyleXML.init(base: baseStyle, ["b" : boldStyle, "i": italicStyle])
415415
groupStyle.xmlAttributesResolver = MyXMLDynamicAttributesResolver()
416416
```
417417

@@ -454,7 +454,7 @@ let styleBold = Style({
454454
$0.color = UIColor.blue
455455
})
456456

457-
let groupStyle = StyleGroup.init(base: styleBase, ["b" : styleBold])
457+
let groupStyle = StyleXML.init(base: styleBase, ["b" : styleBold])
458458
self.textView?.attributedText = sourceHTML.set(style: groupStyle)
459459
```
460460

@@ -536,6 +536,30 @@ This is the result:
536536

537537
<img src="Documentation_Assests/image_6.png" alt="" width=100px/>
538538

539+
Sometimes you may want to provide these images lazily. In order to do it just provide a custom implementation of the `imageProvider` callback in `StyleXML` instance:
540+
541+
```swift
542+
let xmlText = "- <img named=\"check\"/> has done!"
543+
544+
let xmlStyle = StyleXML(base: {
545+
/// some attributes for base style
546+
})
547+
548+
// This method is called when a new `img` tag is found. It's your chance to
549+
// return a custom image. If you return `nil` (or you don't implement this method)
550+
// image is searched inside any bundled `xcasset` file.
551+
xmlStyle.imageProvider = { imageName in
552+
switch imageName {
553+
case "check":
554+
// create & return your own image
555+
default:
556+
// ...
557+
}
558+
}
559+
560+
self.textView?.attributedText = xmlText.set(style: x)
561+
```
562+
539563
<a name="stylemanager"/>
540564

541565
## The `StyleManager`
@@ -546,7 +570,7 @@ This is the result:
546570
Styles can be created as you need or registered globally to be used once you need.
547571
This second approach is strongly suggested because allows you to theme your app as you need and also avoid duplication of the code.
548572

549-
To register a `Style` or a `StyleGroup` globally you need to assign an unique identifier to it and call `register()` function via `Styles` shortcut (which is equal to call `StylesManager.shared`).
573+
To register a `Style` or a `StyleXML` globally you need to assign an unique identifier to it and call `register()` function via `Styles` shortcut (which is equal to call `StylesManager.shared`).
550574

551575
In order to keep your code type-safer you can use a non-instantiable struct to keep the name of your styles, then use it to register style:
552576

@@ -601,7 +625,7 @@ Styles.onDeferStyle = { name in
601625
$0.traitVariants = .italic
602626
}
603627

604-
return (StyleGroup(base: normal, ["bold": bold, "italic": italic]), true)
628+
return (StyleXML(base: normal, ["bold": bold, "italic": italic]), true)
605629
}
606630

607631
return (nil,false)
@@ -628,10 +652,10 @@ has three additional properties:
628652
- `style: StyleProtocol`: you can set it to render the text of the control with an instance of style instance.
629653
- `styledText: String`: use this property, instead of `attributedText` to set a new text for the control and render it with already set style. You can continue to use `attributedText` and set the value using `.set()` functions of `String`/`AttributedString`.
630654

631-
Assigned style can be a `Style`, `StyleGroup` or `StyleRegEx`:
655+
Assigned style can be a `Style`, `StyleXML` or `StyleRegEx`:
632656

633657
- if style is a `Style` the entire text of the control is set with the attributes defined by the style.
634-
- if style is a `StyleGroup` a base attribute is set (if `base` is valid) and other attributes are applied once each tag is found.
658+
- if style is a `StyleXML` a base attribute is set (if `base` is valid) and other attributes are applied once each tag is found.
635659
- if style is a `StyleRegEx` a base attribute is set (if `base` is valid) and the attribute is applied only for matches of the specified pattern.
636660

637661
Typically you will set the style of a label via `Style Name` (`styleName`) property in IB and update the content of the control by setting the `styledText`:

Sources/SwiftRichString/Extensions/AttributedString+Attachments.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,15 @@ public extension AttributedString {
7676
}
7777

7878
let image = Image(named: imageNamed)
79-
let boundsRect = CGRect(string: bounds)
80-
self.init(image: image, bounds: boundsRect)
79+
self.init(image: image, bounds: bounds)
8180
}
8281

8382
/// Initialize a new attributed string from an image.
8483
///
8584
/// - Parameters:
8685
/// - image: image to use.
8786
/// - bounds: location and size of the image, if `nil` the default bounds is applied.
88-
convenience init?(image: Image?, bounds: CGRect? = nil) {
87+
convenience init?(image: Image?, bounds: String? = nil) {
8988
guard let image = image else {
9089
return nil
9190
}
@@ -95,14 +94,23 @@ public extension AttributedString {
9594
#else
9695
var attachment: NSTextAttachment!
9796
if #available(iOS 13.0, *) {
98-
attachment = NSTextAttachment(image: image)
97+
// Due to a bug (?) in UIKit we should use two methods to allocate the text attachment
98+
// in order to render the image as template or original. If we use the
99+
// NSTextAttachment(image: image) with a .alwaysOriginal rendering mode it will be
100+
// ignored.
101+
if image.renderingMode == .alwaysTemplate {
102+
attachment = NSTextAttachment(image: image)
103+
} else {
104+
attachment = NSTextAttachment()
105+
attachment.image = image.withRenderingMode(.alwaysOriginal)
106+
}
99107
} else {
100108
attachment = NSTextAttachment(data: image.pngData()!, ofType: "png")
101109
}
102110
#endif
103111

104-
if let bounds = bounds {
105-
attachment.bounds = bounds
112+
if let boundsRect = CGRect(string: bounds) {
113+
attachment.bounds = boundsRect
106114
}
107115

108116
self.init(attachment: attachment)

Sources/SwiftRichString/Style/StyleGroup.swift

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ import AppKit
3535
import UIKit
3636
#endif
3737

38-
public class StyleGroup: StyleProtocol {
38+
public typealias StyleGroup = StyleXML
39+
40+
public class StyleXML: StyleProtocol {
3941

40-
// The following attributes are ignored for StyleGroup because are read from the sub styles.
42+
// The following attributes are ignored for StyleXML because are read from the sub styles.
4143
public var attributes: [NSAttributedString.Key : Any] = [:]
4244
public var fontData: FontData? = nil
4345
public var textTransforms: [TextTransform]? = nil
@@ -49,21 +51,27 @@ public class StyleGroup: StyleProtocol {
4951
/// to the existing source.
5052
public var baseStyle: StyleProtocol?
5153

52-
/// Parsing options.
54+
/// XML Parsing options.
5355
public var xmlParsingOptions: XMLParsingOptions = []
5456

57+
/// Image provider is called to provide custom image when `StyleXML` encounter a `img` tag image.
58+
/// If not implemented the image is automatically searched inside any bundled `xcassets`.
59+
public var imageProvider: ((String) -> UIImage?)? = nil
60+
5561
/// Dynamic attributes resolver.
5662
/// By default the `StandardXMLAttributesResolver` instance is used.
5763
public var xmlAttributesResolver: XMLDynamicAttributesResolver = StandardXMLAttributesResolver()
5864

5965
// MARK: - Initialization
6066

61-
/// Initialize a new `StyleGroup` with a dictionary of style and names.
67+
/// Initialize a new `StyleXML` with a dictionary of style and names.
6268
/// Note: Ordered is not guarantee, use `init(_ styles:[(String, StyleProtocol)]` if you
6369
/// need to keep the order of the styles.
6470
///
65-
/// - Parameter styles: styles dictionary
66-
public init(base: StyleProtocol? = nil, _ styles: [String: StyleProtocol]) {
71+
/// - Parameters:
72+
/// - base: base style applied to the entire string.
73+
/// - styles: styles dictionary used to map your xml tags to styles definitions.
74+
public init(base: StyleProtocol? = nil, _ styles: [String: StyleProtocol] = [:]) {
6775
self.styles = styles
6876
self.baseStyle = base
6977
}
@@ -133,9 +141,7 @@ public class StyleGroup: StyleProtocol {
133141
/// - Returns: modified attributed string, same instance of the `source`.
134142
public func apply(to attrStr: AttributedString, adding: Bool, range: NSRange?) -> AttributedString {
135143
do {
136-
let xmlParser = XMLStringBuilder(string: attrStr.string, options: xmlParsingOptions,
137-
baseStyle: baseStyle, styles: styles,
138-
xmlAttributesResolver: xmlAttributesResolver)
144+
let xmlParser = XMLStringBuilder(styleXML: self, string: attrStr.string)
139145
return try xmlParser.parse()
140146
} catch {
141147
debugPrint("Failed to generate attributed string from xml: \(error)")

0 commit comments

Comments
 (0)