Created
June 10, 2019 18:09
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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