# Render [![Swift](https://img.shields.io/badge/swift-5.1-orange.svg?style=flat)](#) [![ObjC++](https://img.shields.io/badge/ObjC++-blue.svg?style=flat)](#) [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://opensource.org/licenses/MIT) 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](https://facebook.github.io/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. ```swift func makeCounterBodyFragment(context: Context, coordinator: CounterCoordinator) -> OpaqueNodeBuilder { Component(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` pure function. That means you could wrap any UIView subclass in a vdom node. e.g. ```swift 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. ```swift 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. ```swift 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. ```swift func makeFragment(context: Context) { Component(context: context) { context, coordinator in VStackNode { LabelNode(text: "Foo") Component(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`: ```swift 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: * [facebook/yoga](https://github.com/facebook/yoga)