Skip to content

Commit

Permalink
more playgrounds, cleaner readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Usbergo committed May 23, 2016
1 parent 29dc216 commit e536e45
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 78 deletions.
Binary file not shown.
Binary file added Doc/logo_rect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
135 changes: 135 additions & 0 deletions Playgrounds/05 List Component.playground/Contents.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import UIKit
import XCPlayground
import Render

/*:
![logo](logo_small.png)
# ComponentTableView/CollectionView

Although the approach shown above works perfectly, it does clashes with the React-like component pattern.
`ComponentTableView` and `ComponentCollectionView` expose the same interface and work with a simple array of `ListComponentItemType` (see also `ListComponentItem<ComponentViewType, ComponentStateType>: ListComponentItemType`).
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() `).

*/

func ==(lhs: Album, rhs: Album) -> Bool {
return lhs.id == rhs.id
}
class Album: ComponentStateBase {
let id = NSUUID().UUIDString
let title: String = "Foo"
let artist: String = "Bar"
let cover: UIImage = UIImage(named: "logo_rect")!
var featured: Bool

init(featured: Bool = false) {
self.featured = featured
}
}

// This will just improve the performance in list diffs.
extension Album: ComponentStateTypeUniquing {
var stateUniqueIdentifier: String {
return self.id
}
}

class AlbumComponentView: ComponentView {

// If the component is used as list item it should be registered
// as prototype for the infra.
override class func initialize() {
registerPrototype(component: AlbumComponentView())
}

/// The component state.
var album: Album? { return self.state as? Album }
var featured: Bool { return self.album?.featured ?? false }

/// Constructs the component tree.
override func construct() -> ComponentNodeType {
return ComponentNode<UIView>().configure({ view in
view.style.flexDirection = self.featured ? .Column : .Row
view.backgroundColor = UIColor.blackColor()
view.style.dimensions.width = self.featured ? ~self.parentSize.width/2 : ~self.parentSize.width
view.style.dimensions.height = self.featured ? Undefined : 64
}).children([
ComponentNode<UIImageView>().configure({ view in
view.image = self.album?.cover
view.style.alignSelf = .Center
view.style.dimensions.width = self.featured ? ~self.parentSize.width/2 : 48
view.style.dimensions.height = self.featured ? view.style.dimensions.width : 48
}),
ComponentNode<UIView>().configure({ view in
view.style.flexDirection = .Column
view.style.margin = (0.0, 4.0, 0.0, 4.0, 4.0, 4.0)
view.style.alignSelf = .Center

}).children([
ComponentNode<UILabel>().configure({ view in
view.style.margin = (0.0, 4.0, 0.0, 4.0, 4.0, 4.0)
view.text = self.album?.title ?? "None"
view.textColor = UIColor.whiteColor()
})
])
])
}
}

class ListDemoViewController: UIViewController {

// The item list.
var albums: [ListComponentItemType] = [ListComponentItem<AlbumComponentView, Album>]() {
didSet {
self.listComponentView.renderComponent(self.view.bounds.size)
}
}

/// The collection view component.
lazy var listComponentView: ComponentCollectionView = {
return ComponentCollectionView()
}()

/// Called after the controller'€™s view is loaded into memory.
override func viewDidLoad() {
super.viewDidLoad()

// generate some fake data
self.prepareDummyData()

// configure the list component.
self.listComponentView.configure() {
guard let view = $0 as? ComponentCollectionView else { return }
view.frame.size = view.parentSize
view.backgroundColor = UIColor.blackColor()
view.items = self.albums
}
self.view.addSubview(self.listComponentView)
}

/// Called to notify the view controller that its view has just laid out its subviews.
override func viewDidLayoutSubviews() {
self.listComponentView.renderComponent(self.view.bounds.size)
}
}

extension ListDemoViewController {

//creates some dummy models.
func prepareDummyData() {
var albums = [ListComponentItemType]()
for idx in 0..<4 {
let item = ListComponentItem<AlbumComponentView, Album>(state: Album(featured: idx < 2))
albums.append(item)
}
self.albums = albums
}
}

let vc = ListDemoViewController()
vc.view.frame = CGRect(origin: CGPoint.zero, size:CGSize(width: 500, height: 500))

vc.view



Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions Playgrounds/05 List Component.playground/Sources/Shared.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

import UIKit

public func snapshot(view: UIView) -> UIImage {
view.layoutSubviews()
UIGraphicsBeginImageContext(view.frame.size)
view.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='ios' display-mode='rendered'>
<timeline fileName='timeline.xctimeline'/>
</playground>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
64 changes: 64 additions & 0 deletions Playgrounds/05 List Component.playground/timeline.xctimeline
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=54&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.355752"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=23&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.355964"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=27&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356142"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=27&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356316"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=20&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356484"
lockedSize = "{175, 50}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=20&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356659"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=31&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356824"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=20&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.356992"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=20&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.357161"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=4747&amp;EndingColumnNumber=10&amp;EndingLineNumber=78&amp;StartingColumnNumber=1&amp;StartingLineNumber=78&amp;Timestamp=485699974.357336"
lockedSize = "{338, 345}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=7&amp;CharacterRangeLoc=4737&amp;EndingColumnNumber=8&amp;EndingLineNumber=131&amp;StartingColumnNumber=1&amp;StartingLineNumber=131&amp;Timestamp=485699974.357484"
lockedSize = "{205, 226}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
</TimelineItems>
</Timeline>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
84 changes: 6 additions & 78 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ struct MyComponentState: ComponentStateType {
let expanded: Bool
}

// COMPONENT
class MyComponentView: ComponentView {

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

// View as function of the state.
override func construct() -> ComponentNodeType {

return ComponentNode<UIView>().configure({
$0.style.flexDirection = self.componentState.expanded ? .Row : .Column
$0.backgroundColor = UIColor.blackColor()

}).children([

ComponentNode<UIImageView>().configure({
$0.image = self.componentState?.image
let size = self.componentState.expanded ? self.parentSize.width : 48.0
$0.style.dimensions = (size, size)
}),

ComponentNode<UIView>().configure({
$0.style.flexDirection = .Column
$0.style.margin = (8.0, 8.0, 8.0, 8.0, 0.0, 0.0)

}).children([

ComponentNode<UILabel>().configure({
$0.text = self.componentState?.title ?? "None"
$0.font = UIFont.systemFontOfSize(18.0, weight: UIFontWeightBold)
$0.textColor = UIColor.whiteColor()
}),

ComponentNode<UILabel>().configure({
$0.text = self.componentState?.subtitle ?? "Subtitle"
$0.font = UIFont.systemFontOfSize(12.0, weight: UIFontWeightLight)
$0.textColor = UIColor.whiteColor()
$0.textColor = UIColor.whiteColor()
})
]),

Expand All @@ -107,14 +99,16 @@ class MyComponentView: ComponentView {
$0.text = "2016"
$0.textColor = UIColor.whiteColor()
}))

])
}

}

```

(Check the playground)[Playgrounds/01%20Flexbox%20Components.playground]


The view description is defined by the `construct()` method.

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

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

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

```swift
class ViewControllerWithTableView: UIViewController, UITableViewDataSource, UITableViewDelegate {
var tableView: UITableView = ...
var posts: [Post] = ...
override func viewDidLoad() {
...
tableView.rowHeight = UITableViewAutomaticDimension
...
}
...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let reuseIdentifier = String(PostComponent.self)
let cell: ComponentCell! =
//dequeue a cell with the given identifier
//(remember to use different identifiers for different component classes)
tableView.dequeueReusableCellWithIdentifier(reuseIdentifier) as? ComponentCell ??

//or create a new Cell wrapping the component
ComponentCell(reuseIdentifier: reuseIdentifier, component: PostComponent())

//set the state for the cell
cell.state = posts[indexPath.row]

//and render the component
cell.renderComponent(CGSize(tableView.bounds.size.width))

return cell
}
}
```

###ComponentTableView/CollectionView

Although the approach shown above works perfectly, it does clashes with the React-like component pattern.
`ComponentTableView` and `ComponentCollectionView` expose the same interface and work with a simple array of `ListComponentItemType` (see also `ListComponentItem<ComponentViewType, ComponentStateType>: ListComponentItemType`).
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()).


The example below shows the use of ComponentCollectionView.

```swift
class ViewController: UIViewController {

var items: [ListComponentItemType] = [ListComponentItem<MyComponentView, MyComponentState>]() {
didSet {
// render the list when the items change
listComponentView.renderComponent()
}
}
let listComponentView = ComponentCollectionView()

override func viewDidLoad() {
super.viewDidLoad()

// generate some fake data
for _ in 0..<10 {
let item = ListComponentItem<MyComponentView, MyComponentState>()
item.delegate = self
items.append(item)
}

// configure the list component.
listComponentView.configure() { view in
view.frame.size = view.parentSize
view.items = items
}
view.addSubview(self.listComponentView)
}
}
```
<sup> Check the demo project and the playgrounds for more example. </sup>

<p align="center">
<img src="Doc/list.gif">

[Check the playground](Playgrounds/05%20List%20Component.playground)


#Credits
Expand Down

0 comments on commit e536e45

Please sign in to comment.