GithubHelp home page GithubHelp logo

andrewzhang1992 / render Goto Github PK

View Code? Open in Web Editor NEW

This project forked from alexdrone/render

0.0 2.0 0.0 6.61 MB

Swift and UIKit a la React.

Swift 70.19% Objective-C 2.00% Ruby 0.67% C 27.15%

render's Introduction

Render Logo

Carthage compatible Platform Build Gitter

React-inspired Swift library for writing UIKit UIs.

#Why

From Why React matters:

[The framework] lets us write our UIs as pure function of their states.

Right now we write UIs by poking at them, manually mutating their properties when something changes, adding and removing views, etc. This is fragile and error-prone. [...]

[The framework] lets us describe our entire UI for a given state, and then it does the hard work of figuring out what needs to change. It abstracts all the fragile, error-prone code out away from us.

Installation

Carthage

To install Carthage, run (using Homebrew):

$ brew update
$ brew install carthage

Then add the following line to your Cartfile:

github "alexdrone/Render" "master"    

#TL;DR

Render's building blocks are Components (described in the protocol ComponentViewType).

Despite virtually any UIView object can be a component (as long as it conforms to the above-cited protocol), Render's core functionalities are exposed by the two main Component base classes: ComponentView and StaticComponentView (optimised for components that have a static view hierarchy).

Render layout engine is based on FlexboxLayout.

This is what a component (and its state) would look like:

struct MyComponentState: ComponentStateType {
	let title: String
	let subtitle: String
	let image: UIImage  
	let expanded: Bool
}

// COMPONENT
class MyComponentView: ComponentView {
    
    // The component state.
    var componentState: MyComponentState? {
        return self.state as? MyComponentState
    }
    
    // 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()                		
                })
            ]),
         
            // This node will be part of the tree only when expanded == false. *
            when(!self.componentState?.expanded, ComponentNode<UILabel>().configure({
                $0.style.justifyContent = .FlexEnd
                $0.text = "2016"
                $0.textColor = UIColor.whiteColor()
            }))

        ])
    }
    
}

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.

Every time renderComponent() is called, a new tree is constructed, compared to the existing tree and only the required changes to the actual view hierarchy are performed - if you have a static view hierarchy, you might want to inherit from StaticComponentView to skip this part of the rendering . Also the configure closure passed as argument is re-applied to every view defined in the construct() method and the layout is re-computed based on the nodes' flexbox attributes.

The component above would render to:

Check the playgrounds for more examples

###Lightweight Integration with UIKit

Components are plain UIViews, so they can be used inside a vanilla view hierarchy with autolayout or layoutSubviews. Similarly plain vanilla UIViews (UIKit components or custom ones) can be wrapped in a ComponentNode (so they can be part of a ComponentView or a StaticComponentView).

The framework doesn't force you to use the Component abstraction. You can use normal UIViews with autolayout inside a component or vice versa. This is probably one of the biggest difference from Facebook's ComponentKit.

###Performance & Thread Model

Render's renderComponent() function is performed on the main thread. Diff+Layout+Configuration runs usually under 16ms on a iPhone 4S, which makes it suitable for cells implementation (with a smooth scrolling).

###Live Refresh

You can use Render with Injection in order to have live refresh of your components. Install the injection plugin, patch your project for injection and add this code inside your component class (or in your ViewController):

class MyComponentView: ComponentView {
	...
	func injected() {
		self.renderComponent()
	}
}

###Backend-driven UIs

Given the descriptive nature of Render's components, components can be defined in JSON or XML files and downloaded on-demand. The ComponentDeserializer is being worked on as we speak.

###Components embedded in cells

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

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.

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)
    }
}

Check the demo project and the playgrounds for more example.

#Credits

  • React: The React github page
  • Few.swift: Another React port for Swift. Check it out!
  • css-layout: This project used the C src code for the flexbox layout engine.
  • Dwifft: A swift array diff algorithm implementation.
  • Backend-driven native UIs from JohnSundell: A inspiring video about component-driven UIs (the demo project is also inspired from Spotify's UI).

render's People

Contributors

alexdrone avatar readmecritic avatar

Watchers

James Cloos avatar Andrew avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.