Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/alexdrone/Render

UIKit a-là SwiftUI.framework [min deployment target iOS10]
https://github.com/alexdrone/Render

elm-architecture ios layout-engine reconciliation swift swiftui uikit uiview unidirectional-data-flow virtual-dom

Last synced: about 1 month ago
JSON representation

UIKit a-là SwiftUI.framework [min deployment target iOS10]

Awesome Lists containing this project

README

        

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