Skip to content

Commit e536e45

Browse files
author
Alex Usbergo
committed
more playgrounds, cleaner readme
1 parent 29dc216 commit e536e45

File tree

14 files changed

+230
-78
lines changed

14 files changed

+230
-78
lines changed

Doc/logo_rect.png

47.4 KB
Loading
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import UIKit
2+
import XCPlayground
3+
import Render
4+
5+
/*:
6+
![logo](logo_small.png)
7+
# ComponentTableView/CollectionView
8+
9+
Although the approach shown above works perfectly, it does clashes with the React-like component pattern.
10+
`ComponentTableView` and `ComponentCollectionView` expose the same interface and work with a simple array of `ListComponentItemType` (see also `ListComponentItem<ComponentViewType, ComponentStateType>: ListComponentItemType`).
11+
ComponentTableView/CollectionView takes care of cell reuse for you and apply a diff algorithm when the `items` property is set (so that proper insertions/deletions are performed rather than `reloadData() `).
12+
13+
*/
14+
15+
func ==(lhs: Album, rhs: Album) -> Bool {
16+
return lhs.id == rhs.id
17+
}
18+
class Album: ComponentStateBase {
19+
let id = NSUUID().UUIDString
20+
let title: String = "Foo"
21+
let artist: String = "Bar"
22+
let cover: UIImage = UIImage(named: "logo_rect")!
23+
var featured: Bool
24+
25+
init(featured: Bool = false) {
26+
self.featured = featured
27+
}
28+
}
29+
30+
// This will just improve the performance in list diffs.
31+
extension Album: ComponentStateTypeUniquing {
32+
var stateUniqueIdentifier: String {
33+
return self.id
34+
}
35+
}
36+
37+
class AlbumComponentView: ComponentView {
38+
39+
// If the component is used as list item it should be registered
40+
// as prototype for the infra.
41+
override class func initialize() {
42+
registerPrototype(component: AlbumComponentView())
43+
}
44+
45+
/// The component state.
46+
var album: Album? { return self.state as? Album }
47+
var featured: Bool { return self.album?.featured ?? false }
48+
49+
/// Constructs the component tree.
50+
override func construct() -> ComponentNodeType {
51+
return ComponentNode<UIView>().configure({ view in
52+
view.style.flexDirection = self.featured ? .Column : .Row
53+
view.backgroundColor = UIColor.blackColor()
54+
view.style.dimensions.width = self.featured ? ~self.parentSize.width/2 : ~self.parentSize.width
55+
view.style.dimensions.height = self.featured ? Undefined : 64
56+
}).children([
57+
ComponentNode<UIImageView>().configure({ view in
58+
view.image = self.album?.cover
59+
view.style.alignSelf = .Center
60+
view.style.dimensions.width = self.featured ? ~self.parentSize.width/2 : 48
61+
view.style.dimensions.height = self.featured ? view.style.dimensions.width : 48
62+
}),
63+
ComponentNode<UIView>().configure({ view in
64+
view.style.flexDirection = .Column
65+
view.style.margin = (0.0, 4.0, 0.0, 4.0, 4.0, 4.0)
66+
view.style.alignSelf = .Center
67+
68+
}).children([
69+
ComponentNode<UILabel>().configure({ view in
70+
view.style.margin = (0.0, 4.0, 0.0, 4.0, 4.0, 4.0)
71+
view.text = self.album?.title ?? "None"
72+
view.textColor = UIColor.whiteColor()
73+
})
74+
])
75+
])
76+
}
77+
}
78+
79+
class ListDemoViewController: UIViewController {
80+
81+
// The item list.
82+
var albums: [ListComponentItemType] = [ListComponentItem<AlbumComponentView, Album>]() {
83+
didSet {
84+
self.listComponentView.renderComponent(self.view.bounds.size)
85+
}
86+
}
87+
88+
/// The collection view component.
89+
lazy var listComponentView: ComponentCollectionView = {
90+
return ComponentCollectionView()
91+
}()
92+
93+
/// Called after the controller'€™s view is loaded into memory.
94+
override func viewDidLoad() {
95+
super.viewDidLoad()
96+
97+
// generate some fake data
98+
self.prepareDummyData()
99+
100+
// configure the list component.
101+
self.listComponentView.configure() {
102+
guard let view = $0 as? ComponentCollectionView else { return }
103+
view.frame.size = view.parentSize
104+
view.backgroundColor = UIColor.blackColor()
105+
view.items = self.albums
106+
}
107+
self.view.addSubview(self.listComponentView)
108+
}
109+
110+
/// Called to notify the view controller that its view has just laid out its subviews.
111+
override func viewDidLayoutSubviews() {
112+
self.listComponentView.renderComponent(self.view.bounds.size)
113+
}
114+
}
115+
116+
extension ListDemoViewController {
117+
118+
//creates some dummy models.
119+
func prepareDummyData() {
120+
var albums = [ListComponentItemType]()
121+
for idx in 0..<4 {
122+
let item = ListComponentItem<AlbumComponentView, Album>(state: Album(featured: idx < 2))
123+
albums.append(item)
124+
}
125+
self.albums = albums
126+
}
127+
}
128+
129+
let vc = ListDemoViewController()
130+
vc.view.frame = CGRect(origin: CGPoint.zero, size:CGSize(width: 500, height: 500))
131+
132+
vc.view
133+
134+
135+
Loading
Loading
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
import UIKit
3+
4+
public func snapshot(view: UIView) -> UIImage {
5+
view.layoutSubviews()
6+
UIGraphicsBeginImageContext(view.frame.size)
7+
view.layer.renderInContext(UIGraphicsGetCurrentContext()!)
8+
let image = UIGraphicsGetImageFromCurrentImageContext()
9+
UIGraphicsEndImageContext()
10+
return image
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='ios' display-mode='rendered'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>

Playgrounds/05 List Component.playground/playground.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Timeline
3+
version = "3.0">
4+
<TimelineItems>
5+
<LoggerValueHistoryTimelineItem
6+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=54&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.355752"
7+
selectedRepresentationIndex = "0"
8+
shouldTrackSuperviewWidth = "NO">
9+
</LoggerValueHistoryTimelineItem>
10+
<LoggerValueHistoryTimelineItem
11+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=23&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.355964"
12+
selectedRepresentationIndex = "0"
13+
shouldTrackSuperviewWidth = "NO">
14+
</LoggerValueHistoryTimelineItem>
15+
<LoggerValueHistoryTimelineItem
16+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=27&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356142"
17+
selectedRepresentationIndex = "0"
18+
shouldTrackSuperviewWidth = "NO">
19+
</LoggerValueHistoryTimelineItem>
20+
<LoggerValueHistoryTimelineItem
21+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=27&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356316"
22+
selectedRepresentationIndex = "0"
23+
shouldTrackSuperviewWidth = "NO">
24+
</LoggerValueHistoryTimelineItem>
25+
<LoggerValueHistoryTimelineItem
26+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=20&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356484"
27+
lockedSize = "{175, 50}"
28+
selectedRepresentationIndex = "0"
29+
shouldTrackSuperviewWidth = "NO">
30+
</LoggerValueHistoryTimelineItem>
31+
<LoggerValueHistoryTimelineItem
32+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=20&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356659"
33+
selectedRepresentationIndex = "0"
34+
shouldTrackSuperviewWidth = "NO">
35+
</LoggerValueHistoryTimelineItem>
36+
<LoggerValueHistoryTimelineItem
37+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=31&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356824"
38+
selectedRepresentationIndex = "0"
39+
shouldTrackSuperviewWidth = "NO">
40+
</LoggerValueHistoryTimelineItem>
41+
<LoggerValueHistoryTimelineItem
42+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=20&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356992"
43+
selectedRepresentationIndex = "0"
44+
shouldTrackSuperviewWidth = "NO">
45+
</LoggerValueHistoryTimelineItem>
46+
<LoggerValueHistoryTimelineItem
47+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=20&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.357161"
48+
selectedRepresentationIndex = "0"
49+
shouldTrackSuperviewWidth = "NO">
50+
</LoggerValueHistoryTimelineItem>
51+
<LoggerValueHistoryTimelineItem
52+
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=10&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.357336"
53+
lockedSize = "{338, 345}"
54+
selectedRepresentationIndex = "0"
55+
shouldTrackSuperviewWidth = "NO">
56+
</LoggerValueHistoryTimelineItem>
57+
<LoggerValueHistoryTimelineItem
58+
documentLocation = "#CharacterRangeLen=7&amp;CharacterRangeLoc=4737&amp;EndingColumnNumber=8&amp;EndingLineNumber=131&amp;StartingColumnNumber=1&amp;StartingLineNumber=131&amp;Timestamp=485699974.357484"
59+
lockedSize = "{205, 226}"
60+
selectedRepresentationIndex = "0"
61+
shouldTrackSuperviewWidth = "NO">
62+
</LoggerValueHistoryTimelineItem>
63+
</TimelineItems>
64+
</Timeline>

Playgrounds/RenderPlaygrounds.xcworkspace/contents.xcworkspacedata

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 6 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ struct MyComponentState: ComponentStateType {
5959
let expanded: Bool
6060
}
6161

62-
// COMPONENT
6362
class MyComponentView: ComponentView {
6463

6564
// The component state.
@@ -69,35 +68,28 @@ class MyComponentView: ComponentView {
6968

7069
// View as function of the state.
7170
override func construct() -> ComponentNodeType {
72-
7371
return ComponentNode<UIView>().configure({
7472
$0.style.flexDirection = self.componentState.expanded ? .Row : .Column
7573
$0.backgroundColor = UIColor.blackColor()
76-
7774
}).children([
78-
7975
ComponentNode<UIImageView>().configure({
8076
$0.image = self.componentState?.image
8177
let size = self.componentState.expanded ? self.parentSize.width : 48.0
8278
$0.style.dimensions = (size, size)
8379
}),
84-
8580
ComponentNode<UIView>().configure({
8681
$0.style.flexDirection = .Column
8782
$0.style.margin = (8.0, 8.0, 8.0, 8.0, 0.0, 0.0)
88-
8983
}).children([
90-
9184
ComponentNode<UILabel>().configure({
9285
$0.text = self.componentState?.title ?? "None"
9386
$0.font = UIFont.systemFontOfSize(18.0, weight: UIFontWeightBold)
9487
$0.textColor = UIColor.whiteColor()
9588
}),
96-
9789
ComponentNode<UILabel>().configure({
9890
$0.text = self.componentState?.subtitle ?? "Subtitle"
9991
$0.font = UIFont.systemFontOfSize(12.0, weight: UIFontWeightLight)
100-
$0.textColor = UIColor.whiteColor()
92+
$0.textColor = UIColor.whiteColor()
10193
})
10294
]),
10395

@@ -107,14 +99,16 @@ class MyComponentView: ComponentView {
10799
$0.text = "2016"
108100
$0.textColor = UIColor.whiteColor()
109101
}))
110-
111102
])
112103
}
113104

114105
}
115106

116107
```
117108

109+
(Check the playground)[Playgrounds/01%20Flexbox%20Components.playground]
110+
111+
118112
The view description is defined by the `construct()` method.
119113

120114
`ComponentNode<T>` is an abstraction around views of any sort that knows how to build, configure and layout the view when necessary.
@@ -166,82 +160,16 @@ Given the descriptive nature of **Render**'s components, components can be defin
166160

167161
You can wrap your components in `ComponentTableViewCell` or `ComponentCollectionViewCell` and use the classic dataSource/delegate pattern for you view controller.
168162

163+
[Check the playground](Playgrounds/04%20Components%20embedded%20in%20Cells.playground)
169164

170-
```swift
171-
class ViewControllerWithTableView: UIViewController, UITableViewDataSource, UITableViewDelegate {
172-
var tableView: UITableView = ...
173-
var posts: [Post] = ...
174-
override func viewDidLoad() {
175-
...
176-
tableView.rowHeight = UITableViewAutomaticDimension
177-
...
178-
}
179-
...
180-
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
181-
let reuseIdentifier = String(PostComponent.self)
182-
let cell: ComponentCell! =
183-
//dequeue a cell with the given identifier
184-
//(remember to use different identifiers for different component classes)
185-
tableView.dequeueReusableCellWithIdentifier(reuseIdentifier) as? ComponentCell ??
186-
187-
//or create a new Cell wrapping the component
188-
ComponentCell(reuseIdentifier: reuseIdentifier, component: PostComponent())
189-
190-
//set the state for the cell
191-
cell.state = posts[indexPath.row]
192-
193-
//and render the component
194-
cell.renderComponent(CGSize(tableView.bounds.size.width))
195-
196-
return cell
197-
}
198-
}
199-
```
200165

201166
###ComponentTableView/CollectionView
202167

203168
Although the approach shown above works perfectly, it does clashes with the React-like component pattern.
204169
`ComponentTableView` and `ComponentCollectionView` expose the same interface and work with a simple array of `ListComponentItemType` (see also `ListComponentItem<ComponentViewType, ComponentStateType>: ListComponentItemType`).
205170
ComponentTableView/CollectionView takes care of cell reuse for you and apply a diff algorithm when the `items` property is set (so that proper insertions/deletions are performed rather than reloadData()).
206171

207-
208-
The example below shows the use of ComponentCollectionView.
209-
210-
```swift
211-
class ViewController: UIViewController {
212-
213-
var items: [ListComponentItemType] = [ListComponentItem<MyComponentView, MyComponentState>]() {
214-
didSet {
215-
// render the list when the items change
216-
listComponentView.renderComponent()
217-
}
218-
}
219-
let listComponentView = ComponentCollectionView()
220-
221-
override func viewDidLoad() {
222-
super.viewDidLoad()
223-
224-
// generate some fake data
225-
for _ in 0..<10 {
226-
let item = ListComponentItem<MyComponentView, MyComponentState>()
227-
item.delegate = self
228-
items.append(item)
229-
}
230-
231-
// configure the list component.
232-
listComponentView.configure() { view in
233-
view.frame.size = view.parentSize
234-
view.items = items
235-
}
236-
view.addSubview(self.listComponentView)
237-
}
238-
}
239-
```
240-
<sup> Check the demo project and the playgrounds for more example. </sup>
241-
242-
<p align="center">
243-
<img src="Doc/list.gif">
244-
172+
[Check the playground](Playgrounds/05%20List%20Component.playground)
245173

246174

247175
#Credits

0 commit comments

Comments
 (0)