Giter VIP home page Giter VIP logo

render's Introduction

Render Swift ObjC++ License

Render

CoreRender is a SwiftUI inspired API for UIKit (that is compatible with iOS 10+ and ObjC).

Introduction

  • Declarative: CoreRender uses a declarative API to define UI components. You simply describe the layout for your UI based on a set of inputs and the framework takes care of the rest (diff and reconciliation from virtual view hierarchy to the actual one under the hood).
  • Flexbox layout: CoreRender includes the robust and battle-tested Facebook's Yoga as default layout engine.
  • Fine-grained recycling: Any component such as a text or image can be recycled and reused anywhere in the UI.

TL;DR

Let's build the classic Counter-Example.

The DSL to define the vdom representation is similiar to SwiftUI.

func makeCounterBodyFragment(context: Context, coordinator: CounterCoordinator) -> OpaqueNodeBuilder {
  Component<CounterCoordinator>(context: context) { context, coordinator in
    VStackNode {
      LabelNode(text: "\(coordinator.count)")
        .textColor(.darkText)
        .background(.secondarySystemBackground)
        .width(Const.size + 8 * CGFloat(coordinator.count))
        .height(Const.size)
        .margin(Const.margin)
        .cornerRadius(Const.cornerRadius)
      HStackNode {
        ButtonNode()
          .text("TAP HERE TO INCREASE COUNT")
          .setTarget(coordinator, action: #selector(CounterCoordinator.increase), for: .touchUpInside)
          .background(.systemTeal)
          .padding(Const.margin * 2)
          .cornerRadius(Const.cornerRadius)
      }
    }
    .alignItems(.center)
    .matchHostingViewWidth(withMargin: 0)
  }
}

screen

Label and Button are just specialized versions of the Node<V: UIView> pure function. That means you could wrap any UIView subclass in a vdom node. e.g.

Node(UIScrollView.self) {
  Node(UILabel.self).withLayoutSpec { spec in 
    // This is where you can have all sort of custom view configuration.
  }
  Node(UISwitch.self)
}

The withLayoutSpec modifier allows to specify a custom configuration closure for your view.

Coordinators are the only non-transient objects in CoreRender. They yeld the view internal state and they are able to manually access to the concrete view hierarchy (if one desires to do so).

By calling setNeedsReconcile the vdom is being recomputed and reconciled against the concrete view hiearchy.

class CounterCoordinator: Coordinator{
  var count: UInt = 0

  func incrementCounter() {
    self.count += 1                      // Update the state.
    setNeedsReconcile()                  // Trigger the reconciliation algorithm on the view hiearchy associated to this coordinator.
  }
}

Finally, Components are yet again transient value types that bind together a body fragment with a given coordinator.

class CounterViewCoordinator: UIViewController {
  var hostingView: HostingView!
  let context = Context()

  override func loadView() {
    hostingView = HostingView(context: context, with: [.useSafeAreaInsets]) { context in
      makeCounterBodyFragment(context: context, coordinator: coordinator)
    }
    self.view = hostingView
  }
    
  override func viewDidLayoutSubviews() {
    hostingView.setNeedsLayout()
  }
}

Components can be nested in the node hierarchy.

func makeFragment(context: Context) {
  Component<FooCoordinator>(context: context) { context, coordinator in
    VStackNode {
      LabelNode(text: "Foo")
      Component<BarCoordinator>(context: context) { context, coordinator in
        HStackNode {
          LabelNode(text: "Bar")
          LabelNode(text: "Baz")
        }
      }
    }
  }
}

Use it with SwiftUI

Render nodes can be nested inside SwiftUI bodies by using CoreRenderBridgeView:

struct ContentView: View {
  var body: some View {
    VStack {
      Text("Hello From SwiftUI")
      CoreRenderBridgeView { context in
        VStackNode {
          LabelNode(text: "Hello")
          LabelNode(text: "From")
          LabelNode(text: "CoreRender")
        }
          .alignItems(.center)
          .background(UIColor.systemGroupedBackground)
          .matchHostingViewWidth(withMargin: 0)
      }
      Text("Back to SwiftUI")
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Credits:

Layout engine:

render's People

Contributors

alexdrone avatar byronanderson avatar delebedev avatar esam091 avatar hfossli avatar jconst avatar knpwrs avatar lfarah avatar metabren avatar ooga avatar readmecritic avatar stigi avatar tomaslinhart avatar wanbok avatar zdnk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

render's Issues

Ignored paddings/margins

Hey again :)

I have this issue currently:
screen shot 2017-06-01 at 15 46 04

I tried margins with inner container (code below) and paddings but the right and bottom seems like it is ignored whatever I try. Am I missing something?

    override func construct(state: ApprovalRequestState?, size: CGSize) -> NodeType {
        guard let state = state else {
            return NilNode()
        }
        
        let spacing: CGFloat = 16
        
        let container = Node<UIView>(identifier: "container") { view, layout, size in
            view.backgroundColor = .lightGray
            
            let margin: CGFloat = 8
            layout.marginTop = margin
            layout.marginLeft = margin
            layout.marginRight = margin
            //layout.padding = spacing
            //layout.paddingVertical = spacing
            layout.width = size.width - (2 * margin)
            layout.flexDirection = .column
        }
        
        let innerContainer = Node<UIView>(identifier: "innerContainer") { view, layout, size in
            layout.width = size.width - (2 * spacing)
            layout.margin = spacing
            layout.flexDirection = .column
        }
        
        let icon = Node<UIImageView>(identifier: "requestIcon") { view, layout, _ in
            view.backgroundColor = .blue
            view.layer.cornerRadius = 30
            view.layer.masksToBounds = true
            
            layout.width = 60
            layout.height = 60
        }
        
        let title = Node<UILabel>(identifier: "title") { (view, layout, size) in
            view.text = "Přílič žluťoučký kůň úpěl ďábelské ódy." //state.title
            view.numberOfLines = 0
            view.lineBreakMode = .byWordWrapping
            view.font = UIFont.boldSystemFont(ofSize: 24)
            
        }
        
        let subtitle = Node<UILabel>(identifier: "subtitle") { view, layout, size in
            view.text = "Dalsi" //state.id
            view.textColor = .darkGray
            view.font = UIFont.systemFont(ofSize: 17)
        }
        
        let amount = Node<UILabel>(identifier: "amount") { view, layout, size in
            view.text = "150 000 USD"
            view.font = UIFont.boldSystemFont(ofSize: 28)
            view.textAlignment = .right
            view.clipsToBounds = true
            
            layout.height = 100
        }
        
        innerContainer.add(children: [
            Node<UIView>(identifier: "titleRow") { _, layout, _ in
                layout.flexDirection = .row
                layout.alignItems = .center
                layout.alignContent = .center
            }.add(children: [
                icon,
                Node<UIView>(identifier: "titlesColumn") { _, layout, _ in
                    layout.flexDirection = .column
                    layout.marginLeft = spacing
                }.add(children: [
                    title,
                    subtitle,
                ]),
            ]),
            Node<UIView>(identifier: "amountRow") { _, layout, _ in
                layout.flexDirection = .column
                //layout.justifyContent = .flexEnd
                //layout.alignItems = .flexEnd
                layout.marginTop = spacing
                //layout.marginBottom = spacing
            }.add(children: [
                amount,
            ]),
        ])
        
        return container.add(child: innerContainer)
    }

Can't import framework after CocoaPods install

Setup a fresh iOS Swift Xcode project and then added the framework using CocoaPods. It says it installed successfully, but then when I try to import the framework in my class, I get nothing. It says there's no such module. I simply followed standard procedure with the install, but nothing. Something seem broken.

  1. have latest cocoapods-1.2.1 installed on my system.
  2. Created a single view iOS app
  3. added pod 'Render', '~> 2.7' to my pod file in my app target.
  4. use_frameworks! is there.
  5. tried to import Render in my ViewController class.

Am I going to have to add the framework manually?

Render module warnings

I installed the framework manually since the cocoapod isn't working. But now I get the following warning:

"Umbrella header for module 'Render' does not include header 'YGLayout+Private.h"

So I added #import <Render/YGLayout+Private.h> to 'Render.h' to quiet the warning.

can this be fixed?
Thanks so much for the awesome framework!

Wrapper of ComponentView in a UICollectionViewCell did not wrap correctly

ComponentCollectionViewCell did not replicate the state to the ComponentViewType.
Here's the code for ComponentTableViewCell

  public var state: C.StateType? {
    didSet {
      self.componentView?.state = state
    }
  }

while the same part of code in ComponentCollectionViewCell is missing it

open class ComponentCollectionViewCell<C : ComponentViewType>: UICollectionViewCell {

  public var state: C.StateType? // did not set the state of the wrapper to the actual ComponentView

This is a trivial fix, so I hope it's being fixed soon without need to do a pull request. Thanks

[Bug] Updating state after using NilNode makes the order of items reversed

Sample code using Playground

import PlaygroundSupport

import Render

PlaygroundPage.current.needsIndefiniteExecution = true

struct AppState: StateType {
    var isOn = false
}

class MyView: ComponentView<AppState> {
    override func construct(state: AppState?, size: CGSize) -> NodeType {
        
        let bar = Node<UILabel>() { label, layout, size in
            label.text = "bar"
            
        }
        
        return Node<UIView> { (view, layout, size) in
            
        }
            .add(children: [
                Node<UILabel>() { label, layout, size in
                    label.text = "foo"
                },
                state!.isOn ? bar as NodeType : NilNode()
            ])
    }
}

let container = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
container.backgroundColor = .white

let view = MyView()
view.state = AppState()
view.render(in: container.frame.size)

container.addSubview(view)

view.state?.isOn = true
view.render(in: container.frame.size)

PlaygroundPage.current.liveView = container

Expected:
screen shot 2017-03-12 at 19 18 28

Result:
screen shot 2017-03-12 at 19 15 55

[Question] CollectionNode horizontal scrolling flow layout

Hi Alex,
I have another question. I've been working with CollectionNode and got it working pretty well displaying a set of cards. I can display them in a grid layout and a vertical scrolling flow layout, but I haven't been able to figure out how to change the layout direction and display them in a horizontal scrolling flow layout like a carousel. Is this possible? I tried quite a few Yoga layout commands but wasn't able to change the flow direction.

Any help would be appreciated!

Jason W.

UILabel and view properties not animating inside component

I have a card component that renders with animation options:

        card.render(in: view.bounds.size, options: [
            .animated(duration: 1, options: [.curveEaseInOut]) {
                self.card.center = self.view.center
            }
            ])

Most child components of type, subtype UIView will animate as well:

         let titlebar = Node<UIView> { (view, layout, size) in
                view.backgroundColor = .green
                layout.width = size.width
                layout.aspectRatio = TitleBarSize.InverseProportion
                layout.position = .absolute
                layout.bottom = 0
                layout.alignContent = .center
        }

But child component frame/bounds of type UILabel will not animate, but end up sized correctly at the end of the animation. Does the Render framework not support this?

            let title = Node<UILabel>() { (view, layout, size) in
                view.text = state?.title
                view.numberOfLines = 1
                view.font = Label.font.withSize(size.width * Label.FontSizeRatio)
                view.textColor = UIColor.white
                view.textAlignment = .center
                view.backgroundColor = .darkGray
                layout.width = size.width
                layout.aspectRatio = TitleBarSize.InverseProportion
           }

Also, other properties will not animate such as cornerRadius or in the case above, I'm resizing the font based on the view width, but does not animate the property. Is there a way to do this?

Would like to see more examples of animating component views and their properties.

Thanks in advance! Love the framework so far!

TableNode diff update

I am trying to make the diff update work but whenever I update the state, TableNode scrolls to the top and just updates the rows without animation. Here's my code:

class ListViewComponent: ComponentView<ListPresenter.State> {
    
    override func render() -> NodeType {
        let table = TableNode(reuseIdentifier: "table", parent: self) { view, layout, size in
            layout.width = size.width
            layout.height = size.height
            view.backgroundColor = .yellow
            view.separatorStyle = .none
        }
        
        table.shouldUseDiff = true
        
        table.add(children: state.items.map { request in
            let item = ApprovalRequestState(id: request.id, requestor: request.requestor, subtitle: request.subtitle, amount: request.amount)
            return ComponentNode(ApprovalRequestComponentView(), in: self, reuseIdentifier: "table-item-\(item.id)", key: "table-item-\(item.id)", state: item, size: referenceSize)
        })
        
        return table
    }
    
}

I updating the state like this self.component.set(state: state)

I have Render 4.1

Animations

How would you make transitions/animations between states?

Documentation for new features

It seems like there are new features introduced like animations, console, inspector, TableNode, CollectionNode etc., but documentation is still very basic. Like what is the proper use of reuseIdentifier and key? How to properly use TableNode with diff updates, how to use console and inspector?

Use with CollectionView

Hi, I am trying to create a component which contain a CollectionView but when i am trying to dequeue the cell i am getting the following error:

must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

My code for creating the cell is:

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        
        
        let id = CellPrototype.defaultIdentifier(MyComponent.self)
        
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: id, for: indexPath)
        
        if let cell = cell as? ComponentCollectionViewCell<MyComponent> {
            cell.mountComponentIfNecessary(MyComponent())
            cell.state = self.state?.items[indexPath.row]
            cell.render()
        }
        
        return cell
    }

And this is how i define the UICollectionView inside the component

        func pageScroll() -> NodeType {
            return Node<UICollectionView>(
                create: {[weak self] in
                    let layout = UICollectionViewFlowLayout()
                    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
                    collectionView.backgroundColor = BDColor.transparent
                    collectionView.dataSource = self
                    collectionView.isPagingEnabled = true
                    collectionView.showsHorizontalScrollIndicator = false
                    collectionView.showsVerticalScrollIndicator = false
                    collectionView.alwaysBounceHorizontal = true
                    self?.collectionView = collectionView
                    return collectionView
                },
                configure: {(collection,layout,size) in
                    layout.alignSelf = .stretch
                    layout.flexDirection = .row
                    layout.width = size.width
                    layout.height = size.height
                }
        )}

Anyone know what is the reason for such error?

Thanks!

[Question] Styling

Hey first of all I love what you have made here, really impressive! 🍻

My question: Does anyone have any suggestions for styling an app that uses Render. Specifically styling across multiple components? Either some css (I wish...) like 3d party library you may be using or maybe just how you custom do it to live in harmony with your designer. I've taken a look at StyleKit and NUI but am not sure I'm convinced by either.

I really appreciate any wisdom.

Cheers!
Manny

Gesture driven UI

Is it possible to implement some gesture driven UI with Render? If so, what is proposed approach?

How to return ComponentView from a function?

Is there a way to return a selected instance of componentView from a function without having to recast it later into a concrete types such as ComponentView<BackgroundState>?
Using AnyComponentView as shown doesn't seem to work the way I hoped.

    func makeComponent(select: Int) -> AnyComponentView  {
        switch select {
            case 1:
            return BackgroundComponent()
            case 2:
            return CardComponent()
            case 3:
            return ImageComponent()
            case 4:
            return TitleBarComponent()
        default:
            return DefaultComponent()
        }
    }

I want to dynamically construct a view hierarchy in my viewController.

Corner radius & shadow

How do I setup corner radius if I want shadow at the same time?

public class NavigationCommandView: ComponentView {


    public override func construct() -> ComponentNodeType {
        layer.shadowRadius = 3
        layer.shadowOffset = CGSize(width: 0, height: 0)
        layer.shadowColor = UIColor.blackColor().CGColor
        layer.shadowOpacity = 0.6

        return ComponentNode<UIView>().configure({ (view: UIView) -> Void in
            ....
        })
    }

    override public func layoutSubviews() {
        super.layoutSubviews()

        layer.cornerRadius = bounds.size.height / 2
        layer.masksToBounds = true
    }

}

This wont work because maskToBounds makes shadow invisible because of the mask. And I cannot set conernerRadius to CoMponentNodes view because I dont know its size just yet.

Async layout

Would you consider making layout calculations in background?

Obviously AsyncDisplayKit does that, but more important is that ComponentKit does that too.

Log Warning when using CollectionNode

I just setup a component using CollectionNode with three child nodes added. It compiles and renders fine but I get the following runtime warning in the console:

"A component cell just got updated but the indexpath doesn't seem to be available. This warning is printed 8 times.

Not sure if I'm missing something or if its a bug. Here's my code:

import Foundation
import UIKit
import Render

class Collection: ComponentView<NilState> {
    required init() {
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("Not supported")
    }
    
    override func render() -> NodeType {
        
        let container = CollectionNode(parent: self) { (view, layout, size) in
            view.backgroundColor = UIColor.darkGray
            layout.width = size.width
            layout.height = size.height
        }
        
        let nodes = [
            Node<UIView>(key: "green") { (view, layout, size) in
                layout.width = 70
                layout.height = 70
                view.backgroundColor = UIColor.green
            },
            
            Node<UIView>(key: "red") { (view, layout, size) in
                layout.width = 70
                layout.height = 70
                view.backgroundColor = UIColor.red
            },

            Node<UIView>(key: "blue") { (view, layout, size) in
                layout.width = 70
                layout.height = 70
                view.backgroundColor = UIColor.blue
            }
        ]
        return container.add(children: nodes)
    }
}

Render build fails for "Generic iOS Device" configuration

Using Xcode Version 8.1 (8B62). Opened either the Render or RenderTodoDemo projects attempt to build for "Generic iOS Device", and the build fails at BaseComponentView.swift#69 "Type 'KeyCommands' has no member 'register'".

Possibly this is caused by how the #if (arch(i386) || arch(x86_64)) && (os(iOS) || os(tvOS)) is getting evaluated in KeyCommands.swift ?

How to use Render in CollectionView

Hi Alex,

Let me know about how to use Render in CollectionView, please.

Can we use this method for adding next items when user scroll down on the bottom of the list?
If not, is there another way to do?

public func renderComponentAtIndexPath(indexPath: NSIndexPath) {
self.performBatchUpdates({
self.reloadItemsAtIndexPaths([indexPath])
}, completion: nil)
}

If our collectionView supported the horizontal scroll UI, what sort of stuff should we care about so that we can create with Render?

Different behaviour for setting margin with CSSEdgeAll vs. individual left/top/right/bottom

I'm observing that when I css_setMargin(value, for:CSSEdgeAll) the margin values do not appear to take effect, whereas when I set individual left/top/right/bottom margins with individual css_setMargin() calls, the margins work properly.

I expect this is likely an issue with the Facebook library, but I wanted to report it in case I misunderstood how to use CSSEdgeAll and/or if you had encountered this issue.

The "flex" style property vs. flex-grow and flex-shrink

I'm familiar with the FlexBox model (at least I think I am), but I don't understand how the "flex" property works in this implementation given that there doesn't appear to be a corresponding property in CSS.

In the FlexBoxLayout project that Render incorporates (https://github.com/alexdrone/FlexboxLayout) there is an example which shows two components expressing "flex" property values of 0.8 and 0.2 respectively. I assume this means that the first component wants to take 80% of the available space and the other component 20% of the space? Is that what it means? If so, does the flex property have priority over the other properties?

Lastly, is there any plans to support flex-grow and flex-shrink?

ComponentView as ComponentNode?

Is it meant to be used like that If I want new ComponentView described with another ComponentView subclasses?

struct SomeComponentState: ComponentStateType {

    var text: String?

}

class SomeComponentView: StaticComponentView {

    var someState: SomeComponentState?

    override func construct() -> ComponentNodeType {
        return ComponentNode<UILabel>().configure({
            $0.backgroundColor = .blackColor()
            $0.textColor = .whiteColor()
            $0.text = self.someState?.text
        })
    }

}

struct FinalComponentState: ComponentStateType {

    var text: String?
    var showsDetail: Bool = false

}

class FinalComponentView: ComponentView {

    var finalState = FinalComponentState(text: "works!", showsDetail: true)

    override func construct() -> ComponentNodeType {
        return ComponentNode<UIView>().configure({
            $0.backgroundColor = .redColor()
        }).children([
            ComponentNode<UIView>().configure({
                // some code here
            }),
            when(self.finalState.showsDetail, ComponentNode<SomeComponentView>().configure({
                $0.someState = SomeComponentState(text: self.finalState.text)
            }))
        ])
    }
}

TableNode with diff error

Hey,
I have issue with TableNode again. This is the setup:

    override func render() -> NodeType {
        let table = TableNode(
            reuseIdentifier: "list-table",
            key: "list-table",
            in: self,
            cellReuseEnabled: true,
            autoDiffEnabled: false
        ) { view, layout, size in
            layout.width = size.width
            layout.height = size.height
            
            view.backgroundColor = .clear
        }
        
        let nodes = state.approvals.map { item -> NodeType? in
            do {
                return try self.nodeFactory.create(for: item, in: self)
            } catch let error {
                print(error)
                return nil
            }
        }.flatMap { $0 } // remove nils from array
        
        table.add(children: nodes)
        
        return table
    }

This is also what nodeFactory.create(for:, in:) does:

        let state = GenericApprovalsListItemState(
            id: entity.id,
            title: entity.headline,
            subtitle: entity.subheadline,
            iconName: entity.iconName,
            currency: nil,
            amount: nil,
            itemDescription: entity.itemDescription,
            note: entity.note,
            escalation: .none,
            attachments: nil
        )
        
        let component = GenericApprovalsListItemComponentView()
        
        return ComponentNode(
            component,
            in: parent,
            key: "list-item-midas-\(entity.id)",
            state: state
        )

I am just pushing a new state with updated array of items and adding the children. Every time all of them again - I hope that is right.
It ends up with:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (18) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (6 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).

Also setting up the contentInset does not seem to work.

Also is there a recommended way how to add UIRefreshControl?

[Bug] Emptying a node's children causes it to incorrectly measure its size

Code

import PlaygroundSupport

import Render

PlaygroundPage.current.needsIndefiniteExecution = true

struct AppState: StateType {
    var isOn = true
}

class MyView: ComponentView<AppState> {
    override func construct(state: AppState?, size: CGSize) -> NodeType {
        
        let makeChild = {
            return Node<UILabel>() { label, _, _ in
                label.text = "title"
            }
        }
        
        let children = [
            makeChild(),
            makeChild(),
            makeChild(),
            makeChild()
        ]
        
        return Node().add(children: [
            Node() { view, layout, size in
                view.backgroundColor = .blue
                layout.height = 50
            },
            Node().add(children: state!.isOn ? children : []),
            Node() { view, layout, size in
                view.backgroundColor = .green
                layout.height = 50
            }
        ])
    }
}

let container = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
container.backgroundColor = .white

let view = MyView()
view.state = AppState()
view.render(in: container.frame.size)

container.addSubview(view)

view.state?.isOn = !view.state!.isOn
view.render(in: container.frame.size)

PlaygroundPage.current.liveView = container

Expected:

screen shot 2017-03-18 at 12 04 46

Actual:

screen shot 2017-03-18 at 12 05 21

This happens because when the container is empty, it becomes a leaf node, and leaf nodes are measured by using sizeThatFits:. UIView's implementation only reports its current size.

One solution that I've tried is by setting view's size to zero before marking dirty and it works.

Issue rendering layout with nested components at multiple depths

Hello,

First off thanks for the AWESOME library! So I recently updated my version of Render from 3.2 to 4.3 which wasn't too bad but now it seems like the Yoga values I set for "layout" seem to now be ignored for any of my nested "children" components! Is this a known issue? I saw that in the notes for the 4.3 release this exact issue may have been fixed but I don't see any relevant code for the fix included with the commit? Any ideas? Cheers! -mo

minDimensions

Whenever I set maxDimensions or use renderComponent(_ size: CGSize) it renders the component/node in that size/dimensions.

Seems like a bug to me. I need it to set max width of the view and use wrapping.

I am not setting minDimensions or dimensions, just maxDimensions.

Using Yoga via cocoa pods

Hey! I'm the maintainer of Yoga and just stumbled upon this project. Cool stuff! I'm wondering if there is a reason for not using Yoga via cocoa pods and instead having a fork of the code? Cocoa pods support in Yoga is quite new so that might be the case :)

[Feature Request] React style component Lifecycle hooks

I was reading through the React documentation and they talked about adding lifecycle hooks to a class: https://facebook.github.io/react/docs/state-and-lifecycle.html

with componentDidMount()
and componentWillUnmount()

These component methods would be very handy in addition to the willUpdate() and didUpdate() methods already available.

For example, I want to trigger animations when I create and present a component to the screen and also I want to trigger another just before the component is removed.

Thanks for your consideration! :)

Table cell height with Buffer

Hey,

I am using Buffer together with Render. However my cells dont adopt the size of the actual ComponentView inside. The height stays as defined by default :(
Heres my setup:

class ListComponentView: ComponentView<ListPresenter.State> {
    
    override func construct(state: ListPresenter.State?, size: CGSize) -> NodeType {
        guard let state = state else {
            return NilNode()
        }
        
        let tableView = Node<TableView<ApprovalRequestState>> { view, layout, size in
            let list = state.items.map({ (request) -> ListItem<ApprovalRequestState> in
                let itemState = ApprovalRequestState(id: request.id, title: "", escalated: false)
                return ListItem<ApprovalRequestState>(
                    type: ComponentTableViewCell<ApprovalRequestComponentView>.self,
                    container: view,
                    state: itemState,
                    configurationClosure: self.configureCell
                )
            })
            
            view.elements = list
            
            layout.width = size.width
            layout.height = size.height
        }
        
        return tableView
    }
    
}

extension ListComponentView {
    
    fileprivate func configureCell(_ cell: ComponentTableViewCell<ApprovalRequestComponentView>, _ state: ApprovalRequestState) {
        cell.mountComponentIfNecessary(ApprovalRequestComponentView())
        cell.state = state
        cell.render()
    }
    
}
struct ApprovalRequestState: StateType {
    
    let id: String
    let title: String
    let escalated: Bool
    
}

extension ApprovalRequestState: Equatable {
    
}

func ==(lhs: ApprovalRequestState, rhs: ApprovalRequestState) -> Bool {
    return lhs.id == rhs.id
}

class ApprovalRequestComponentView: ComponentView<ApprovalRequestState> {
    
    override func construct(state: ApprovalRequestState?, size: CGSize) -> NodeType {
        guard let state = state else {
            return NilNode()
        }
        
        let title = Node<UILabel> { (view, layout, size) in
            view.text = "\(state.id)"
            view.textColor = state.escalated ? .red : .black
            
            layout.width = size.width
            layout.height = 71
        }
        
        return Node<UIView> { (_, layout, size) in
            layout.flexDirection = .row
            layout.width = size.width
        }.add(child: title)
    }
    
}

[Bug] Changing node children count from non-zero to zero crashes the app

import PlaygroundSupport

import Render

PlaygroundPage.current.needsIndefiniteExecution = true

struct AppState: StateType {
    var isOn = false
}

class MyView: ComponentView<AppState> {
    override func construct(state: AppState?, size: CGSize) -> NodeType {
        
        let children = state!.isOn ? []: [Node()]
        
        return Node().add(children: children)
    }
}

let container = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
container.backgroundColor = .white

let view = MyView()
view.state = AppState()
view.render(in: container.frame.size)

container.addSubview(view)

view.state?.isOn = true
view.render(in: container.frame.size)

PlaygroundPage.current.liveView = container

The crash happens here. It seems that even though this is a leaf node, yoga.numberOfChildren still returns the number of children from previous state.

If you need some help just let me know 🙂

[Documentation Request] Render Options - Animation

Hi Alex,
I really appreciate all the new docs and sample code you made available its quite helpful and I will be making changes to my code to follow your guides.

I would however like to know more or see code snippets on how to make use of the RenderOptions, especially in regards to animation. When I make changes to the view hierarchy I would like to know how to trigger animation transitions from one state or configuration to the next especially when altering the layout of child components.

I saw examples of gesture triggered animation in your code examples but nothing that helps me understand how to trigger animation when the layout or other changes occur!

Thanks for your help! This framework is really maturing! :)

Jason W.

JSON Serialization

Hey Alex!

Lovely library you made here. I saw you were looking to add json deserialization from files or json?

My library https://github.com/OatmealCode/Oatmeal has that built in. You could read components from files/apis/cache and convert them back into objects! Let me know if you decide to give it a whirl, and feel free to reach out!

Event handlers

Hi,
I am considering to move from storyboards to Render for part of my app UI. Currently everything works fine for me and i managed to build some nice UI with it easily. The only thing that is missing is best practices on how to handle events. in my UI i have two buttons: one for signing in and one for signing up and i wanted to know what is the recommended way to handle the on tap event with Render.
Please consider the fact that ReSwift is also big part in my architecture so when the user will click on sign in then i will dispach an action.

Thanks.

[Question] Re-render stateless component

Hello,
I'm trying to understand how I can re-render UILabel from the component after receiving new props 'counter' in the component.
demo: https://youtu.be/YyDkfDA9U2M

My VC

import ReSwift
import Render

class CounterViewController: UIViewController, StoreSubscriber, ComponentController {

    var component = CounterComponentView()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        store.subscribe(self)
        addComponentToViewControllerHierarchy()
        component.delegate = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func newState(state: State) {
        component.counter = state.counterState.counter
    }

    override func viewDidLayoutSubviews() {
        renderComponent()
    }

    func configureComponentProps() {
        component.counter = store.state.counterState.counter
    }
    
    func incrementCounter() {
        store.dispatch(CounterActionIncrease())
    }
    
    func decrementCounter() {
        store.dispatch(CounterActionDecrease())
    }
}

extension CounterViewController: CounterComponentViewDelegate {
    func increment() {
        self.incrementCounter()
    }
    func decrement() {
        self.decrementCounter()
    }
}

View component

protocol CounterComponentViewDelegate: class {
    func increment()
    func decrement()
}

func counterLabel(text: String) -> NodeType {
    return Node<UILabel>(reuseIdentifier: "counterLabel") { view, layout, size in
        layout.alignSelf = .center
        layout.marginBottom = 16
        view.text = text
    }
}

func button(name: String, onPress: @escaping () -> ()) -> NodeType {
    return Node<UIButton>(reuseIdentifier: "button") { view, layout, size in
        layout.justifyContent = .center
        layout.alignItems = .center
        layout.width = 20
        view.setTitle(name, for: .normal)
        view.setTitleColor(Color.black, for: .normal)
        view.onTap { _ in
            onPress()
        }
    }
}

class CounterComponentView: StatelessComponentView {
    
    var counter: Int = 0
    weak var delegate: CounterComponentViewDelegate?
    
    required init() {
        super.init()
        defaultOptions = [.preventViewHierarchyDiff]
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func render() -> NodeType {
        let container = Node<UIView> { view, layout, size in
            layout.paddingHorizontal = 20
            layout.justifyContent = .center
            layout.alignSelf = .center
        }
        let actions = Node<UIView> { view, layout, size in
            layout.flexDirection = .row
            layout.justifyContent = .spaceBetween
        }
        let label = counterLabel(text: "\(self.counter)")
        let incrementBtn = button(name: "+") {
            self.delegate?.increment()
            print("counter: \(self.counter)")
        }
        let decrementBtn = button(name: "-") {
            self.delegate?.decrement()
            print("counter: \(self.counter)")
            
        }
        
        actions.add(children: [incrementBtn, decrementBtn])
        return container.add(children: [label, actions])
    }
}

I'm from the world of js (react-native) and I just starting to learn swift.
Maybe I'm doing something wrong.
Thanks for the help

Performance claims are dubious, or example project has a very bad bug

First off very awesome job, regardless of performance issues what you've got here so far is very promising.

I loaded up the demo and while the initial load of the demo is decently quick ( no warnings about components ) if you turn on device rotation and rotate the device there is a huge amount of lag while it re-renders the components.

Secondly I find the performance claim a little bit ambiguous, just loading the sample app up takes a good 10-15s before the white screen is rendered out on an iPhone 6S in release mode.

Nested ComponentViews have incorrect layout

I think to make any reasonably sized app using Render, I would need the ability to nest ComponentViews inside other ComponentViews. (The strategy in the ReadMe of using a function that returns a ComponentNode is insufficient because the inner Component needs to also be able to track and update its own state.)

There are no examples that I can see in the repo, so I've taken a stab in a way that makes sense intuitively. However, there seems to be an issue that the inner view thinks the parent view's size (referenceSize) is (0,0). Here's a simplified playground:

import UIKit
import PlaygroundSupport
import Render

class InnerView: ComponentView {
    override func construct() -> ComponentNodeType {
        return ComponentNode<UIView>().configure{ view in
            view.backgroundColor = UIColor.blue
            view.style.minDimensions.height = 5
            view.style.margin = (10, 10, 10, 10, 10, 10)
            view.style.flex = 1
        }
    }
}

class OuterView: ComponentView {
    override func construct() -> ComponentNodeType {
        return ComponentNode<UIView>().configure{ view in
            view.backgroundColor = UIColor.red
        }.children([ComponentNode<InnerView>().configure{ view in
            view.backgroundColor = UIColor.green
            view.style.minDimensions.height = 50
            view.style.margin = (10, 10, 10, 10, 10, 10)
        }])
    }
}

let component = OuterView()
component.renderComponent(withSize: CGSize(width: 100, height: 100))
PlaygroundPage.current.liveView = component

With this code, I'd expect to see a blue view inside a green view inside a red view, which I do. But also, the blue view should expand to fill the size of its green container (- margin space), because flex = 1. Like so:

screen shot 2016-11-13 at 7 28 51 pm

However, the inner blue view thinks that it's parent's size is (0,0), so I instead get this:

screen shot 2016-11-13 at 7 29 46 pm

The flex property ends up being meaningless and the view just takes the size of its minDimension. (Inexplicably, though, the horizontal layout is still correct?)


My question: Is this how I should be creating nested ComponentViews? Am I missing some technique here? Could the problem be that renderComponent never gets called on the innerView, and if so, where should I put that call?

UIScrollview postRender

I have a tree of component views that are added to a UIScrollView at runtime.
I see that there are postRender methods in extensions (i.e. Reset.swift) that look like they are designed to automatically resize a scroll view to properly host the rendered layout. However, it isn't clear to me how to take advantage of this. I currently manually call the intrinsicSize method after rendering to determine the layout size and adjust my scroll view, but this is inefficient because calling intrinsicSize causes a separate rendering of the tree to occur.

I can see that the postRender method of the extension is never invoked. What do I need to do to get my scroll views to automatically adjust? Rather than adding my root component view as a subview of my scroll view, should I be constructing the scroll view itself as a component view?

CollectionNode not displaying all of its cells until scrolled off screen and back again

I created an array of card components and configured and rendered them externally, then passed them into my Collection component as nodes where they are added as children to CollectionNode. When I run the app I have 8 cards in the elements array, but only 1 card will display on screen, which is unexpected. If I swipe up and cause the view to scroll upwards so that the non displaying cards move off screen and then back on again, the other cards appear where they are supposed to be.

I'm guessing the cards reentering the screen cause them to refresh/render whereas they initially did not. I looked for a way to update CollectionNode, but nothing I tried made a difference.

Here is my code if it helps:

import Foundation
import UIKit
import Render

struct CollectionState: StateType {
    var id: String = "collectionContainer"
    var elements: [NodeType] = [layoutBuilder("")]
    var layoutAs: String = "flow"
}

class CollectionContainer: ComponentView<CollectionState> {
    required init() {
        super.init()
        self.state = CollectionState()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("Not supported")
    }
    
    override func render() -> NodeType {
        
        let collection = CollectionNode(key: self.state.id, parent: self, create: {
            let collectionView = UICollectionView(frame: .zero, collectionViewLayout: self.setFlowLayout())
            collectionView.showsHorizontalScrollIndicator = false
            collectionView.showsVerticalScrollIndicator = false
            collectionView.clipsToBounds = false
            collectionView.backgroundColor = UIColor.clear
            collectionView.reloadData()
            return collectionView
            
        }) { (view, layout, size) in
            layout.percent.width = 100%
            layout.percent.height = 100%
            configs[self.state.layoutAs]!(layout)
        }
        return collection.add(children: self.state.elements)
    }
    
    func setFlowLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsetsMake(30, 10, 10, 10)
        layout.minimumInteritemSpacing = 10
        layout.minimumLineSpacing = 15
        return layout
    }
}

You'll notice I'm customizing the collectionView, CollectionNode is using with the create: closure so I can control the layout and set other properties, however not everything works as expected.

I tried to get access to the cell's view so that I could set clipsToBounds to False, since it clips the shadows off my cards, but I'm not sure how to properly customize cells using CollectionNode.
As always your help is so appreciated! :)

TableNode and CollectionNode stop working when nested inside of a parent node

I'm trying to nest a TableNode or CollectionNode inside a parent node like this:

return parent.add(children: [collection.add(children: cells)])

But then the child cells stop rendering as if the datasource is inactive.

Indeed, when I setup a manual implementation of UICollectionView and nest it inside of a parent node as above, its datasource methods aren't called at all. Perhaps, this has something to do with ComponentTableViewCell and ComponentCollectionViewCell.

Would appreciate any insight you might have!
thanks, Jason W.

Need Collection View Examples

Would like to see an example of a collection view as a component in one of the demos or in the documentation. Tried to implement one but got stuck. (See my code in issue #39 )
Wish it was as easy to implement as TableNode.

Thanks for the great framework Alex! I'm liking it more and more! 👍

Jason W.

CreateBlock not being called on Node after update.

I was using the createBlock to perform some initialization and execute an animation like this:

let card = Node<UIView>(key: state.id,
                                create: {
            let cardView = UIView()
            ComponentAnimation.springOn(view: cardView)
            return cardView

but now this never gets called after I pulled the latest update. Should I be using another block, like props: for this?

I need to execute this animation just once when my card is created so that it pops on screen. If I put it out in the configuration block it gets called multiple times.

Thanks for your help.

Consider adding `updateState` convenience method

React provides the setState method which allows easily setting a subset, or all, of a component's state and triggering a render. Similarly, Few.swift provides the updateState function (but unfortunately does not make it easy to set just a piece of the state object). These functions, while simple, are valuable because they encourage a certain paradigm.

I personally use the following:

class Ref<T> {
  var value: T

  init(_ value: T) {
    self.value = value
  }
}

extension ComponentViewType {
  func updateState<T: ComponentStateType>(@noescape block: (Ref<T?>) -> ()) {
    let castedState = state as? T
    let reference = Ref(castedState)
    block(reference)
    state = reference.value
    renderComponent(CGSize.undefined)
  }
}

So then updateState can then be called like so:

struct BookState: ComponentStateType {
  var title: String
  var author: String
}

// then in a button callback or something:
updateState{ (state: Ref<BookState?>) in
  state.value!.title = "new title"
}

as opposed to currently needing to write:

var newState = myState!
newState.node = node
state = newState
renderComponent()

The (state: Ref<BookState?>) is necessary because the infra doesn't know what your ComponentStateType subclass is, but this can be solved if the state type is made a generic parameter of the view type, as Few.swift does.

If you think this seems reasonable I'd be happy to open a pull request! Interested to hear your thoughts.

[Feature Request] Inspector highlights changes in state

I was playing around with the new Inspector app that you built. I know this is going to be super helpful for my projects, because we are building very complex component hierarchies.

One Feature that would really be helpful is to see nodes that changed state or were re-rendred, and highlighted in some way to make it obvious which parts of the hierarchy are being updated.

thanks for your consideration,

Jason W,

Questions about `prune(view)`

Hey. Nice work!

I have not used your framework yet. I've only read the source as I have another closed source project (closed for now) in objc which also takes great inspiration from react.

About this method:

func prune(view: UIView) {
if !viewSet.contains(view) {
view.removeFromSuperview() //todo: put in a global reusable pool?
} else {
for subview in view.subviews where subview.hasFlexNode {
prune(subview)
}
}
}

A UIScrollView has two UIImageView's as subviews – the scroll indicators. There are many types of views which uses subviews internally. UIButton, UITableView, etc etc. Do you take that into account when reusing views?

In my framework I'm thinking that I might need to have separate rules for reusing and splitting up the different types of views depending on its structure.

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.