diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index 3de17a12..0a8b30ab 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 66AE19D21CC8C6B300B78B5E /* SVGParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AE19D11CC8C6B300B78B5E /* SVGParser.swift */; }; AD9EDEE3709C1668BED1B1BF /* Pods_Macaw.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA114AD1025D7DA0B0DC35A8 /* Pods_Macaw.framework */; }; B0086C561C789758008A4F69 /* GroupRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0086C551C789758008A4F69 /* GroupRenderer.swift */; }; + B0090DBF1CEA1EDD0018D77C /* ObservableList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0090DBE1CEA1EDD0018D77C /* ObservableList.swift */; }; B03275E51C78772F000BDD33 /* Align.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03275B21C78772F000BDD33 /* Align.swift */; }; B03275E61C78772F000BDD33 /* AspectRatio.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03275B31C78772F000BDD33 /* AspectRatio.swift */; }; B03275E71C78772F000BDD33 /* Baseline.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03275B41C78772F000BDD33 /* Baseline.swift */; }; @@ -103,6 +104,7 @@ 66AE19D11CC8C6B300B78B5E /* SVGParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SVGParser.swift; path = svg/SVGParser.swift; sourceTree = ""; }; 8A680FA7BCDE96402C10D8D2 /* Pods-Macaw.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Macaw.release.xcconfig"; path = "Pods/Target Support Files/Pods-Macaw/Pods-Macaw.release.xcconfig"; sourceTree = ""; }; B0086C551C789758008A4F69 /* GroupRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupRenderer.swift; sourceTree = ""; }; + B0090DBE1CEA1EDD0018D77C /* ObservableList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableList.swift; sourceTree = ""; }; B02E75D61C160F8D00D1971D /* Macaw.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Macaw.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B02E76131C1612A900D1971D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B03275B21C78772F000BDD33 /* Align.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Align.swift; sourceTree = ""; }; @@ -237,6 +239,14 @@ name = svg; sourceTree = ""; }; + B0090DBD1CEA1ECE0018D77C /* bindings */ = { + isa = PBXGroup; + children = ( + B0090DBE1CEA1EDD0018D77C /* ObservableList.swift */, + ); + path = bindings; + sourceTree = ""; + }; B02E75CC1C160F8D00D1971D = { isa = PBXGroup; children = ( @@ -259,6 +269,7 @@ isa = PBXGroup; children = ( 66AE19D01CC8C66E00B78B5E /* svg */, + B0090DBD1CEA1ECE0018D77C /* bindings */, 5742238C1CA540F8001EE463 /* animation */, B03275B01C78772F000BDD33 /* model */, 585C04461C27CFB100335FF2 /* views */, @@ -567,6 +578,7 @@ B03275ED1C78772F000BDD33 /* FontWeight.swift in Sources */, B03275F61C78772F000BDD33 /* Cubic.swift in Sources */, 66AE19D21CC8C6B300B78B5E /* SVGParser.swift in Sources */, + B0090DBF1CEA1EDD0018D77C /* ObservableList.swift in Sources */, 574223901CA5448C001EE463 /* Animation.swift in Sources */, 585C04481C27CFB100335FF2 /* MacawView.swift in Sources */, ); diff --git a/Source/bindings/ObservableList.swift b/Source/bindings/ObservableList.swift new file mode 100644 index 00000000..faeac01c --- /dev/null +++ b/Source/bindings/ObservableList.swift @@ -0,0 +1,212 @@ +// +// https://github.com/safx/ObservableArray-RxSwift +// ObservableArray.swift +// ObservableArray +// +// Created by Safx Developer on 2015/02/19. +// Copyright (c) 2016 Safx Developers. All rights reserved. +// + +import Foundation +import RxSwift + +public struct ArrayChangeEvent { + public let insertedIndices: [Int] + public let deletedIndices: [Int] + public let updatedIndices: [Int] + + private init(inserted: [Int] = [], deleted: [Int] = [], updated: [Int] = []) { + assert(inserted.count + deleted.count + updated.count > 0) + self.insertedIndices = inserted + self.deletedIndices = deleted + self.updatedIndices = updated + } +} + +public struct ObservableArray: ArrayLiteralConvertible { + public typealias EventType = ArrayChangeEvent + + internal var eventSubject: PublishSubject! + internal var elementsSubject: PublishSubject<[Element]>! + internal var elements: [Element] + + public init() { + self.elements = [] + } + + public init(count:Int, repeatedValue: Element) { + self.elements = Array(count: count, repeatedValue: repeatedValue) + } + + public init(_ s: S) { + self.elements = Array(s) + } + + public init(arrayLiteral elements: Element...) { + self.elements = elements + } + + public init(array elements: [Element]) { + self.elements = elements + } +} + +extension ObservableArray { + public mutating func rx_elements() -> Observable<[Element]> { + if elementsSubject == nil { + self.elementsSubject = PublishSubject<[Element]>() + } + return elementsSubject + } + + public mutating func rx_events() -> Observable { + if eventSubject == nil { + self.eventSubject = PublishSubject() + } + return eventSubject + } + + private func arrayDidChange(event: EventType) { + elementsSubject?.onNext(elements) + eventSubject?.onNext(event) + } +} + +extension ObservableArray: Indexable { + public var startIndex: Int { + return elements.startIndex + } + + public var endIndex: Int { + return elements.endIndex + } +} + +extension ObservableArray: RangeReplaceableCollectionType { + public var capacity: Int { + return elements.capacity + } + + public mutating func reserveCapacity(minimumCapacity: Int) { + elements.reserveCapacity(minimumCapacity) + } + + public mutating func append(newElement: Element) { + elements.append(newElement) + arrayDidChange(ArrayChangeEvent(inserted: [elements.count - 1])) + } + + public mutating func appendContentsOf(newElements: S) { + let end = elements.count + elements.appendContentsOf(newElements) + guard end != elements.count else { + return + } + arrayDidChange(ArrayChangeEvent(inserted: Array(end..(newElements: C) { + guard !newElements.isEmpty else { + return + } + let end = elements.count + elements.appendContentsOf(newElements) + arrayDidChange(ArrayChangeEvent(inserted: Array(end.. Element { + let e = elements.removeLast() + arrayDidChange(ArrayChangeEvent(deleted: [elements.count])) + return e + } + + public mutating func insert(newElement: Element, atIndex i: Int) { + elements.insert(newElement, atIndex: i) + arrayDidChange(ArrayChangeEvent(inserted: [i])) + } + + public mutating func removeAtIndex(index: Int) -> Element { + let e = elements.removeAtIndex(index) + arrayDidChange(ArrayChangeEvent(deleted: [index])) + return e + } + + public mutating func removeAll(keepCapacity: Bool = false) { + guard !elements.isEmpty else { + return + } + let es = elements + elements.removeAll(keepCapacity: keepCapacity) + arrayDidChange(ArrayChangeEvent(deleted: Array(0..(subRange: Range, with newCollection: C) { + let oldCount = elements.count + elements.replaceRange(subRange, with: newCollection) + guard let first = subRange.first else { + return + } + let newCount = elements.count + let end = first + (newCount - oldCount) + subRange.count + arrayDidChange(ArrayChangeEvent(inserted: Array(first.. Element? { + let e = elements.popLast() + if e != nil { + arrayDidChange(ArrayChangeEvent(deleted: [elements.count])) + } + return e + } +} + +extension ObservableArray: CustomDebugStringConvertible { + public var description: String { + return elements.description + } +} + +extension ObservableArray: CustomStringConvertible { + public var debugDescription: String { + return elements.debugDescription + } +} + +extension ObservableArray: CollectionType { + public subscript(index: Int) -> Element { + get { + return elements[index] + } + set { + elements[index] = newValue + if index == elements.count { + arrayDidChange(ArrayChangeEvent(inserted: [index])) + } else { + arrayDidChange(ArrayChangeEvent(updated: [index])) + } + } + } + + public subscript(bounds: Range) -> ArraySlice { + get { + return elements[bounds] + } + set { + elements[bounds] = newValue + guard let first = bounds.first else { + return + } + arrayDidChange(ArrayChangeEvent(inserted: Array(first.. - public var contents: [Node] { - get { return contentsVar.value } - set(val) { contentsVar.value = val } - } + public var contents: ObservableArray public init(contents: [Node] = [], pos: Transform = Transform(), opaque: NSObject = true, visible: NSObject = true, clip: Locus? = nil, tag: [String] = []) { - self.contentsVar = Variable<[Node]>(contents) + self.contents = ObservableArray(array: contents) super.init( pos: pos, opaque: opaque, diff --git a/Source/render/GroupRenderer.swift b/Source/render/GroupRenderer.swift index 4dec6af8..41851142 100644 --- a/Source/render/GroupRenderer.swift +++ b/Source/render/GroupRenderer.swift @@ -20,13 +20,13 @@ class GroupRenderer: NodeRenderer { func onGroupChange(new: [Node]) { ctx.view.setNeedsDisplay() } - group.contentsVar.asObservable().subscribeNext { new in - onGroupChange(new) + group.contents.rx_events().subscribeNext { new in + onGroupChange(self.group.contents.elements) }.addDisposableTo(disposeBag) } func render() { - let contentRenderers = group.contents.map { RenderUtils.createNodeRenderer($0, context: ctx) } + let contentRenderers = self.group.contents.map { RenderUtils.createNodeRenderer($0, context: ctx) } contentRenderers.forEach { renderer in if let rendererVal = renderer { CGContextSaveGState(ctx.cgContext)