Skip to content

Instantly share code, notes, and snippets.

@gregomni
Created June 10, 2019 18:09
Show Gist options
  • Select an option

  • Save gregomni/ea240f80d21fd893eedb511037d7ec5d to your computer and use it in GitHub Desktop.

Select an option

Save gregomni/ea240f80d21fd893eedb511037d7ec5d to your computer and use it in GitHub Desktop.
An example of matching up @State values from multiple View trees. SwiftUI probably does something faster/cleaner/nicer, but this is how it could work.
import UIKit
// Just hold a value in a class so that we can reference it.
private class XStateStorage<Value> {
var value: Value
init(value: Value) {
self.value = value
}
}
// Something like SwiftUI's View but leaving out the fancy function builder stuff for simplicity
protocol XView {
var body: [XView] { get }
}
// A resulting struct for converting things that conform to XView into an explicit tree
struct XViewTree {
var parent: XView
var children: [XViewTree]
}
class XViewTreeGenerator {
static var current: XViewTreeGenerator? // Could/should be thread-local instead
// Here we store where we are currently rendering the tree, and all of the state in the tree
var currentPath = IndexPath()
var allState: [NSIndexPath : AnyObject] = [:]
// Convert a root XView into an XViewTree. You'd do this on each render.
func generateTree(root: XView) -> XViewTree {
func internalGenerateTree(root: XView) -> XViewTree {
var children: [XViewTree] = []
var index = 0
for child in root.body {
currentPath.append(index)
children.append(generateTree(root: child))
currentPath.removeLast()
index += 1
}
return XViewTree(parent: root, children: children)
}
XViewTreeGenerator.current = self
let result = internalGenerateTree(root: root)
XViewTreeGenerator.current = nil
return result
}
// For use just by XState property delegates
func getCurrentState() -> AnyObject? {
return allState[currentPath as NSIndexPath]
}
func setCurrentState(_ state: AnyObject) {
allState[currentPath as NSIndexPath] = state
}
}
// XState finds its actual state storage in the current generator based on the current position in the view tree.
// Note that for simplicity, this example implementation only works for a single @XState value per view. Multiple
// values per view would require some additional index path munging and checking.
@propertyDelegate
public struct XState<Value> {
private var initial: Value
private func getStorage() -> XStateStorage<Value> {
guard let generator = XViewTreeGenerator.current else { fatalError("Accessing @XState outside of body") }
if let storage = generator.getCurrentState() as! XStateStorage<Value>? {
print("\(generator.currentPath) matched up to existing storage: \(storage.value)")
return storage
} else {
print("\(generator.currentPath) creating new storage with value: \(initial)")
let storage = XStateStorage(value: initial)
generator.setCurrentState(storage)
return storage
}
}
public var value: Value {
get { getStorage().value }
set { getStorage().value = newValue }
}
public init(initialValue: Value) {
initial = initialValue
}
}
// Here are some sample XView types.
struct XChildView : XView {
@XState var isThing = true
var body: [XView] {
print(isThing)
return []
}
}
struct XIntermediateView : XView {
@XState var stuff = "String"
var body: [XView] {
print(stuff)
return [XChildView(), XChildView()]
}
}
struct XContentView : XView {
var body: [XView] {
return [XChildView(), XIntermediateView(), XChildView()]
}
}
// See how the state is created on first render, and matched up to the already existing state on re-render, even though the XViews themselves are new.
let g = XViewTreeGenerator()
g.generateTree(root: XContentView())
g.generateTree(root: XContentView())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment