EDIT: I have updated the example to be more complete, but I'm still not sure if that works how I'm imagining it. I'll first try to implement a working version of the UI library before coming back with more details.
Hello everyone!
The idea is to enable a thin layer of syntactic sugar to render component trees. Why? It presents them in a really intuitive and visually easing structure.
One strong use case for this feature: It would allow to create some nifty libraries for rendering declarative layout. (Basically a Swift version of React.)
As far as I can see, this would not introduce any backward-incompatible changes.
Basic example:
Given
protocol Properties {}
protocol Renderable {
init(properties: Properties, children: [RenderTree]?)
func render() -> [RenderTree]?
}
class RenderTree {
typealias Factory = (Properties, [RenderTree]?) -> Renderable
var factory: Factory
var properties: Properties
var children: [RenderTree]?
init(factory: @escaping Factory, properties: Properties, _ children: [RenderTree]? = nil) {
self.factory = factory
self.properties = properties
self.children = children
}
}
class Scroll: Renderable {
struct ScrollProperties: Properties {}
var children: [RenderTree]?
static func createElement(_ children: [RenderTree]? = nil) -> RenderTree {
print("create \(type(of: self))")
return RenderTree(
factory: Scroll.init,
properties: ScrollProperties(),
children
)
}
required init(properties _: Properties, children: [RenderTree]?) {
self.children = children
}
func render() -> [RenderTree]? {
print("Hello \(type(of: self))")
return nil
}
}
class Section: Renderable {
struct SectionProperties: Properties {
let heading: String
}
var properties: SectionProperties
var children: [RenderTree]?
static func createElement(heading: String, _ children: [RenderTree]? = nil) -> RenderTree {
print("create \(type(of: self))")
return RenderTree(
factory: Section.init,
properties: SectionProperties(heading: heading),
children
)
}
required init(properties: Properties, children: [RenderTree]?) {
self.properties = properties as! SectionProperties
self.children = children
}
func render() -> [RenderTree]? {
print("Hello \(type(of: self)) \(properties.heading)")
return nil
}
}
class Label: Renderable {
struct LabelProperties: Properties {
let text: String
}
var properties: LabelProperties
var children: [RenderTree]?
static func createElement(text: String, _ children: [RenderTree]? = nil) -> RenderTree {
print("create \(type(of: self))")
return RenderTree(
factory: Label.init,
properties: LabelProperties(text: text),
children
)
}
required init(properties: Properties, children _: [RenderTree]?) {
self.properties = properties as! LabelProperties
}
func render() -> [RenderTree]? {
print("Hello \(type(of: self)) \(properties.text)")
return nil
}
}
func render(_ root: RenderTree) {
root.factory(root.properties, root.children).render()
render(root.children)
}
func render(_ children: [RenderTree]?) {
if let children = children {
for child in children {
for (property, value) in Mirror(reflecting: child.properties).children {
print("\(property!): \(value)")
}
child.factory(child.properties, child.children).render()
render(child.children)
}
}
}
evaluate
let foo = "Some"
let bar = "labels"
let baz = "Some"
let bat = "more"
let tree = (
<Scroll>
<Section heading="Section one">
<Label text{foo}/>
<Label text{bar}/>
</Section>
<Section heading="Section two">
<Label text{baz}/>
<Label text{bat}/>
</Section>
<Section heading="Section tree"/>
</Scroll>
)
render(tree)
the same as
let foo = "Some"
let bar = "labels"
let baz = "Some"
let bat = "more"
let tree = (
Scroll.createElement([
Section.createElement(heading: "Section one", [
Label.createElement(text: foo),
Label.createElement(text: bar),
]),
Section.createElement(heading: "Section two", [
Label.createElement(text: baz),
Label.createElement(text: bat),
]),
Section.createElement(heading: "Section tree"),
])
)
render(tree)
See JSX and https://reactjs.org/docs/jsx-in-depth.html for further examples and the rationale to why one would want to implement this, considering one could fake it with template literals.
Interested to hear your thoughts on it!