Skip to content

Commit 172fa65

Browse files
authored
Merge pull request #3 from contentful-labs/feature/tutorial
Add in-depth usage tutorial
2 parents 24e4fe9 + 0f26e1f commit 172fa65

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ let viewController = RichTextViewController(richText: yourRichTextField,
5757

5858
Initializing a new `RenderingConfiguration()` provides a configuration with sane defaults. Simply create an instance, then mutate it to further customize the rendering.
5959

60+
### In-depth tutorial
61+
62+
For a more comprehensive tutorial on how to use the library, view the [tutorial](TUTORIAL.md) in the root directory of the project.
63+
6064
### Example
6165

6266
The best way to get acquainted with using this library in an iOS app is to check out the [example project code](Example/RendererExample/ViewController.swift). In particular, pay attention to the view provider and inline provider in order to learn how to render entries and assets that are embedded in the rich text.

TUTORIAL.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Integrating rich-text-renderer.swift into an iOS app
2+
3+
## The minimum configuration
4+
5+
The main entry point for the library is the `RichTextViewController`. You can either use it standalone, or subclass it. The `view` instance for `RichTextViewController` has a `UITextView` subview that uses a custom `NSLayoutManager` and `NSTextContainer` to lay out text, enabling text to wrap around nested views for embedded assets and entries, and also enabling blockquote styling analogous to that seen on websites.
6+
7+
## The minimum configuring for RichTextViewController
8+
9+
`RichTextViewController` needs to use a `RenderingConfiguration` instance to infer how various types of rich text nodes should be rendered. This configuration determines what fonts should be used for the various headings and paragraphs, how to style hyperlinks, and how to render nested entries and assets via your custom `ViewProvider` and `InlineProvider` instances. Initializing a new `RenderingConfiguration()` provides a configuration with sane defaults. Simply create an instance, then mutate it to further customize the rendering.
10+
11+
To render rich text, the minimum requirement is to initialize an instance that conforms `RichTextRenderer` to pass to your instance of `RichTextViewController`. The rendering library provides a default renderer, `DefaultRichTextRenderer`, configured to use pre-configured renderers for each node type. The below code is an example of a very basic configuration.
12+
13+
```swift
14+
var styling = RenderingConfiguration()
15+
styling.viewProvider = MyViewProvider()
16+
styling.inlineResourceProvider = MyInlineProvider()
17+
let renderer = DefaultRichTextRenderer(styleConfig: styling)
18+
let viewController = RichTextViewController(richText: yourRichTextField,
19+
renderer: renderer,
20+
nibName: nil,
21+
bundle: nil)
22+
```
23+
24+
### Rendering custom views for embedded entries and assets
25+
26+
If you do not poplulate the `viewProvider` or `inlineResourceProvider` properties of the rendering configuration structure, an empty view with a frame of `CGRect.zero` will be rendered for embedded entries and assets, and an empty string will be rendered for inline & hyperlink entries and assets. In order to customize the rendering for entries and assets that have been inlined, hyperlinked, or embedded into a rich text document, custom types conforming to `ViewProvider` and `InlineProvider` must be attached to the instance of `RenderingConfiguration` that is being used. Both protocols only require one method for conformance.
27+
28+
```swift
29+
public protocol InlineProvider {
30+
func string(for resource: FlatResource, context: [CodingUserInfoKey: Any]) -> NSMutableAttributedString
31+
}
32+
33+
public protocol ViewProvider {
34+
/// This function should return the view which will be rendered for a `Contentful.ResourceLinkBlock` node.
35+
func view(for resource: FlatResource, context: [CodingUserInfoKey: Any]) -> ResourceBlockView
36+
}
37+
```
38+
39+
The function for `ViewProvider` requires that you return a `ResourceBlockView` rather than a simple `UIView`. `ResourceBlockView` is declared as a union type of `UIView` and `ResourceLinkBlockRepresentable`. The reason for this union type is that views corresponding to embedded entries and assets must be able to resize themselves according to the content inside and the width provided given the space in the rich text's superview. You can require that views span the entire width of the superview, or that text wraps around those views by setting the `surroundingTextShouldWrap` property. The declarations of `ResourceBlockView` and `ResourceLinkBlockRepresentable` are below.
40+
41+
```swift
42+
/// A union type that bridges the current operating system's view-type with `ResourceLinkBlockRepresentable`.
43+
public typealias ResourceBlockView = ResourceLinkBlockRepresentable & View
44+
45+
/// Views should conform to this protocol so that `ResourceLinkBlockRenderer` instances can apply the
46+
/// correct `NSAttributedString.Key`, view pairs to the range of characters which comprise a `Contentful.ResourceLinkBlock` node.
47+
public protocol ResourceLinkBlockRepresentable {
48+
49+
/// This boolean determines if text should wrap along the sides of this view (if that view doesn't stretch the width of the superview),
50+
/// or if text should continue below.
51+
var surroundingTextShouldWrap: Bool { get }
52+
53+
/// The passed-in context, useful for determining your own rendering logic.
54+
var context: [CodingUserInfoKey: Any] { get set }
55+
56+
/// This method is called by `RichTextViewController` and is passed a width value which represents the
57+
/// available width within the superview. Implementing this method is useful for applying the proper height to the view based on its width.
58+
func layout(with width: CGFloat)
59+
}
60+
```
61+
62+
## Writing custom renderers
63+
64+
While initializing a new `DefaultRichTextRenderer` structure provides sane default renderers for all node types, there may be scenarios where it is preferable to completely override a renderer for a specific node type. All node renderers conform to the node renderer protocol:
65+
66+
```swift
67+
public protocol NodeRenderer {
68+
/// Method to render a node. This method is called by instances of `RichTextRenderer` to delegate
69+
/// node rendering to the correct renderer for a given node.
70+
func render(node: Node, renderer: RichTextRenderer, context: [CodingUserInfoKey: Any]) -> [NSMutableAttributedString]
71+
}
72+
```
73+
74+
Once you have implemented a renderer, for instance a `CustomHeadingRenderer: NodeRenderer`, then simply set the relevant property to use your custom renderer instead of the default:
75+
76+
```swift
77+
var styling = RenderingConfiguration()
78+
styling.viewProvider = MyViewProvider()
79+
styling.inlineResourceProvider = MyInlineProvider()
80+
var renderer = DefaultRichTextRenderer(styleConfig: styling)
81+
renderer.headingRenderer = CustomHeadingRenderer() // override the heading renderer here
82+
```
83+
84+
To get an idea of how to implement a `NodeRenderer`, check out the source code for the renderers provided by the library.
85+
86+
Once your instance of `RichTextRenderer`, either a fully custom type, or a `DefaultRichTextRenderer` structure that has been customized, pass it to `RichTextViewController`.
87+
88+
## Triggering the rendering cycle
89+
90+
To render a `RichTextDocument` object, simply set the `richText` property on your view controller instance, either during initialization, or later:
91+
92+
During initialization:
93+
94+
```swift
95+
let viewController = RichTextViewController(richText: yourRichTextField,
96+
renderer: renderer,
97+
nibName: nil,
98+
bundle: nil)
99+
```
100+
101+
or after initialization:
102+
103+
```
104+
let viewController = RichTextViewController(richText: nil,
105+
renderer: renderer,
106+
nibName: nil,
107+
bundle: nil)
108+
parentViewController.present(viewController, animated: true, completion: nil)
109+
viewController.richText = richTextDocumentInstance
110+
```
111+
112+
Please note that if the rich text is set after the view has been shown on screen, that no special loading state is rendered by default. It is up to you to render any other subviews based on state.

0 commit comments

Comments
 (0)