Developer-Friendly SwiftUI Node Editor
EasyNodeEditor
Still in alpha stage of development!
EasyNodeEditor is a library for creating node editors with SwiftUI. I am developing this library with the goal of showing as little internal complex code as possible, so that developers can concentrate as much as possible on node creation.
Demo
Usage1 – Standard output node
Step 1. Create your node
Create a class, and inherit it from NodeModelBase class.
class YourOutputNode: NodeModelBase {
}
Step 2. Create Outputs
Create output variables.
Put @Output
for output variables.
Make sure you put @objc
wrapper to all your output variables.
There are no restrictions on the naming of the variables. Name the variables whatever you want, and the library will automatically use that name to display in the node.
class YourOutputNode: NodeModelBase {
@objc @Output var output: Int = 3
}
Step 3. Register your node
Register your node when you instanciate EasyNodeEditor View.
struct ContentView: View {
var body: some View {
EasyNodeEditor(nodeTypes: [YourOutputNode.self])
}
}
This is it! The EasyNodeEditor Library will create a node like this.
Usage2 – Standard input and output node
Step 1. Create your node
Create a class, and inherit it from NodeModelBase class.
class YourIONode: NodeModelBase {
}
Step 2. Create Inputs and Outputs
Create inputs and/or outputs.
Put @Input
for input variables, and @Output
for output variables.
Make sure you put @objc
wrapper to all your input and output variables.
There are no restrictions on the naming of the variables. Name the variables whatever you want, and the library will automatically use that name to display in the node.
class YourIONode: NodeModelBase {
@objc @Input var input: Int = 0
@objc @Output var output: Int = 0
}
Step 3. Define what happens when input value changes
Override processOnChange()
function, and define your process.
Don’t change input value inside processOnChange()
. It will start infinite loop.
class YourIONode: NodeModelBase {
@objc @Input var input: Int = 0
@objc @Output var output: Int = 0
override func processOnChange() {
output = input * 5
}
}
Step 4. Register your node
Register your node when you instanciate EasyNodeEditor View.
struct ContentView: View {
var body: some View {
EasyNodeEditor(nodeTypes: [YourOutputNode.self, YoutIONode.self])
}
}
Very easy!! The EasyNodeEditor Library will create a node like this.
Usage 3 – Standard display node
Step 1. Create your node
Create a class, and inherit it from NodeModelBase class.
class YourDisplayNode: NodeModelBase {
}
Step 2. Create Inputs
Create inputs.
Put @Input
for input variables.
Make sure you put @objc
wrapper to all your input variables.
There are no restrictions on the naming of the variables. Name the variables whatever you want, and the library will automatically use that name to display in the node.
class YourDisplayNode: NodeModelBase {
@objc @Input var input: Int = 0
}
Step3. Create View
Override middleContent()
function, and define your View.
class YourDisplayNode: NodeModelBase {
@objc @Input var input: Int = 0
override func middleContent() -> AnyView {
return AnyView(
Group {
Text("number is now -> \(input)")
}
)
}
}
Step 4. Register your node
Register your node when you instanciate EasyNodeEditor View.
struct ContentView: View {
var body: some View {
EasyNodeEditor(nodeTypes: [YourOutputNode.self, YoutIONode.self, YourDisplayNode.self])
}
}
Amazing!! The EasyNodeEditor Library will create a node like this.
Usage 4 – Standard Interactive Node
I assume you have read Usage 1 ~ 3 here.
For interactive nodes, EasyNodeEditor provides @Middle
property wrapper.
Whenever the value of the variables with @Input
or @Middle
changes, processOnChange()
function will fire.
If you need a binding object for interaction, create a class which inherits ObservableObject
and define a @Published
variable inside. Variables defined directly inside your node class will not be bindable.
After finished making, register your node as usual.
class YourInteractiveNodeSubModel: ObservableObject {
@Published var sliderValue: Double = 0.0
}
class YourInteractiveNode: NodeModelBase {
@objc @Input var input: Int = 0
@ObservedObject var subModel = YourInteractiveNodeSubModel()
@objc @Middle var count: Int = 0
@objc @Output var output: Int = 0
override func processOnChange() {
output = input * count
}
override func middleContent() -> AnyView {
return AnyView(
Group {
Slider(value: self.$subModel.sliderValue, in: 0...100, onEditingChanged: { changed in
self.count = Int(self.subModel.sliderValue)
})
}
.frame(minWidth: 200, maxWidth: 200)
.fixedSize()
)
}
}
Simple!! The EasyNodeEditor Library will create a node like this.
Full Sample Code
import SwiftUI
import EasyNodeEditor
struct ContentView: View {
var body: some View {
EasyNodeEditor(nodeTypes: [YourOutputNode.self, YourIONode.self, YourDisplayNode.self, YourInteractiveNode.self])
}
}
class YourOutputNode: NodeModelBase {
@objc @Output var output: Int = 3
}
class YourIONode: NodeModelBase {
@objc @Input var input: Int = 0
@objc @Output var output: Int = 0
override func processOnChange() {
output = input * 5
}
}
class YourDisplayNode: NodeModelBase {
@objc @Input var input: Int = 0
override func middleContent() -> AnyView {
return AnyView(
Group {
Text("number is now -> \(input)")
}
)
}
}
class YourInteractiveNodeSubModel: ObservableObject {
@Published var sliderValue: Double = 0.0
}
class YourInteractiveNode: NodeModelBase {
@objc @Input var input: Int = 0
@ObservedObject var subModel = YourInteractiveNodeSubModel()
@objc @Middle var count: Int = 0
@objc @Output var output: Int = 0
override func processOnChange() {
output = input * count
}
override func middleContent() -> AnyView {
return AnyView(
Group {
Slider(value: self.$subModel.sliderValue, in: 0...100, onEditingChanged: { changed in
self.count = Int(self.subModel.sliderValue)
})
}
.frame(minWidth: 200, maxWidth: 200)
.fixedSize()
)
}
}