Swift version of yjs.
y-uniffi is a great yjs Swift implementation though, I have created a completely Swift reimplementation of yjs, as y-uniffi dose not yet have a nested Map, UndoManager, etc. implementation.
Now this library is based on yjs and implemented in Swift for a personal use and is not intended to be fully compatible with yjs.
dependencies: [
.package(url: "https://github.com/ObuchiYuki/yswift.git", branch: "1.0.0"),
]
- Supported collaborative types:
- Text
- text insertion
- embedded elements insertion
- insertion of formatting attributes
- observe events and deltas
- Map
- insertion, update and removal of primitive JSON-like elements
- recursive insertion, update and removal of other collaborative elements of any type
- observe events and deltas
- deep observe events bubbling up from nested collections
- Array
- insertion and removal of primitive JSON-like elements
- recursive insertion of other collaborative elements of any type
- observe events and deltas
- deep observe events bubbling up from nested collections
- move index positions
- XML Types (Intentionally not supported)
- Sub documents
- Transaction origin
- Undo/redo manager
- Encoding formats:
- lib0 v1 encoding
- lib0 v2 encoding
- Transaction events:
- on event update
- on after transaction
- Tests
- yjs tests
- doc.tests
- encoding.tests
- snapshot.tests
- undo-redo.tests
- udpates.tests
- y-array.tests
- y-map.tests
- y-test.tests
- yswift tests
- y-array.swift.tests
- y-map.swift.tests
- y-object.swift.tests
- integration.swift.tests
- yjs tests
Swift implementation of YArray
final public class YArray<Element: YElement>
// init with array literal
let intArray: YArray<Int> = [1, 2]
print(intArray) // [1, 2]
// index access
print(intArray[1]) // 2
// append Element
intArray.append(3)
print(intArray) // [1, 2, 3]
// append Sequence
intArray.append(contentsOf: [4, 5])
print(intArray) // [1, 2, 3, 4, 5]
// range access
print(intArray[2...]) // [3, 4, 5]
// use as Sequence
for i in intArray {
print(i) // 1, 2 ...
}
// YArray with YMap type
let mapArray: YArray<YMap<Int>> = [
["Apple": 160]
]
mapArray.append(["Banana": 240])
Swift implementation of YMap
final public class YMap<Value: YElement>
// init with dictionary literal
let intMap: YMap<Int> = [
"Apple": 160
]
// subscript access
intMap["Banana"] = 240
print(intMap["Apple"]) // 160
// nested map
let arrayMap: YMap<YArray<Int>> [
"Alice": [12, 24],
"Bob": [24, 64, 75]
]
Binding to classes based on YMap
open class YObject: YOpaqueObject
class Person: YObject {
// Syncronized property
@Property var name: String = ""
// nested proeprty
@WProperty var children: YArray<Person> = []
required init() {
super.init()
self.register(_name, "name")
self.register(_children, "children")
}
convenience init(name: String) {
self.init()
self.name = name
}
}
let person = Person(name: "Alice")
// can use as Combine Publisher
person.$name
.sink{ print("name is \($0)") }.store(in: &bag)
person.$children
.sink{ print("children is \($0)") }.store(in: &bag)
// update to property to sync
person.name = "Bob"
// update nested type to sync
person.children.append(Person(name: "Bobson"))
Store a reference to an object.
class Layer: YObject {
@Property var parent: YReference<Layer>? = nil
@WProperty var children: YArray<Person> = []
func addChild(_ child: Layer) {
self.children.append(child)
// make Reference
child.parent = YReference(self)
}
...
}
let root = Layer("root")
root.addChild(Layer("child0"))
// copy dosen't change a reference.
let copiedRoot = root.copy()
// fail
assert(copiedRoot.children[0].parent.value === copiedRoot)
// smart copy changes a reference.
let smartCopiedRoot = root.smartCopy()
// success
assert(smartCopiedRoot.children[0].parent.value === smartCopiedRoot)
YElement
is a protocol that is inherited by values that can be YArray
, YMap
values, and YObject
properties.
public protocol YElement {
/// Make opaque data concrete.
static func fromOpaque(_ opaque: Any?) -> Self
/// Make concrete data opaque.
func toOpaque() -> Any?
}
You can use YCodable
to turn a Codable
value into a YElement
, or YRawRepresentable
to turn an enum into a YElement
.
struct Point: YCodable {
var x: CGFloat
var y: CGFloat
}
let array = YArray<Point>()
array.append(Point(x: 1, y: 3))
enum LayerKind: String, YRawRepresentable {
case rect
case text
case path
}
let map = YMap<LayerKind>()
map["rect"] = .rect
map["text"] = .text
Or you can create a YElement
by defining your own encoding and decoding.
enum Delta<T: YElement>: YElement {
case by(T)
case to(T)
public func toOpaque() -> Any? {
switch self {
case .by(let value): return ["by": value.toOpaque()]
case .to(let value): return ["to": value.toOpaque()]
}
}
public static func fromOpaque(_ opaque: Any?) -> Self {
let (key, value) = (opaque as! [String: Any?]).first
if (key == "by") { return .by(T.fromOpaque(value)) }
if (key == "to") { return .to(T.fromOpaque(value)) }
fatalError("Unexpected case.")
}
}