alexdrone / render Goto Github PK
View Code? Open in Web Editor NEWUIKit a-là SwiftUI.framework [min deployment target iOS10]
UIKit a-là SwiftUI.framework [min deployment target iOS10]
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
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
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?
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:
However, the inner blue view thinks that it's parent's size is (0,0), so I instead get this:
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?
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
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.
Am I going to have to add the framework manually?
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.
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.
Seems the LICENSE file is being required/included?
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?
Render/Render/ComponentCell.swift
Lines 173 to 177 in befc59d
If our collectionView supported the horizontal scroll UI, what sort of stuff should we care about so that we can create with Render?
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)
}
}
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)
}
}
Hey again :)
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)
}
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
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.
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?
Is it possible to implement some gesture driven UI with Render? If so, what is proposed approach?
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
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.
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.
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 ?
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!
It would be cool to add the Manual installation guide in your README.md
. You can take a look at my iOS Readme Template to see how you can do it.
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.
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!
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! :)
How would you make transitions/animations between states?
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:
Render/Render/ComponentView.swift
Lines 71 to 79 in a6c7086
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.
Would you consider making layout calculations in background?
Obviously AsyncDisplayKit does that, but more important is that ComponentKit does that too.
I'd be cool to also add Cocoapods support. I done this implementation myself last month for my library. Just follow this post and this checklist.
Cool project! Not using it but wanted to suggest to replace your own implementation of FlexBox with SwiftBox. Sorry if you've considered it and it doesn't make sense. It's always good to have less code to maintain.
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)
}))
])
}
}
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.
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.
The latest version is still 0.52 in cocoapods specs. Can you update it to 1.0 ?
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
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
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.
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.
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!
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?
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?
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 🙂
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.
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,
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.
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.
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! :)
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!
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 :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.