Skip to content

Commit

Permalink
Tree protocol refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
srdanrasic committed Mar 3, 2019
1 parent e5d49a1 commit 1533ede
Show file tree
Hide file tree
Showing 25 changed files with 634 additions and 502 deletions.
4 changes: 2 additions & 2 deletions Bond.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|

s.name = "Bond"
s.version = "7.2.0"
s.version = "7.3.0"
s.summary = "A Swift binding framework"

s.description = <<-DESC
Expand All @@ -21,7 +21,7 @@ Pod::Spec.new do |s|
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.11"
s.tvos.deployment_target = "9.0"
s.source = { :git => "https://github.com/SwiftBond/Bond.git", :tag => "7.2.0" }
s.source = { :git => "https://github.com/SwiftBond/Bond.git", :tag => "7.3.0" }
s.source_files = "Sources/**/*.{h,m,swift}", "Supporting Files/Bond.h"
s.ios.exclude_files = "Sources/Bond/AppKit"
s.tvos.exclude_files = "Sources/Bond/AppKit"
Expand Down
68 changes: 40 additions & 28 deletions Bond.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//: [Previous](@previous)

import Foundation
import UIKit
import Bond

// Tree node is a tree with a single root

var t = TreeNode("Child 00", [
TreeNode("Child 000"),
TreeNode("Child 001", [
TreeNode("Child 0010")
]),
TreeNode("Child 002", [
TreeNode("Child 0020", [
TreeNode("Child 00200")
]),
TreeNode("Child 0021")
])
])

print(t.dfsView.map { $0.value })
print(t.bfsView.map { $0.value })

print(t.dfsView.firstIndex(where: { $0.value.hasSuffix("200") })!)
print(t.dfsView.allSatisfy { $0.value.starts(with: "Child") })
print(t.dfsView.randomElement()!)

// Tree node is a tree with multiple roots

var ta = TreeArray<String>([
t,
TreeNode("Child 01", [
TreeNode("Child 010")
])
])

print(ta.dfsView.map { $0.value })
print(ta.bfsView.map { $0.value })

print(ta.dfsView.firstIndex(where: { $0.value.hasSuffix("200") })!)
print(t.dfsView.allSatisfy { $0.value.starts(with: "Child") })
print(t.dfsView.randomElement()!)

// Custom trees

extension UIView: TreeProtocol {

public var children: [UIView] {
return subviews
}
}

let v = UIPickerView()

print(v.dfsView.map { type(of: $0) })
print(v.bfsView.map { type(of: $0) })

print(v[childAt: [0, 0]])


//: [Next](@next)
1 change: 1 addition & 0 deletions Playground-iOS.playground/contents.xcplayground
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
<page name='UICollectionView+ObservableArray2D'/>
<page name='Key Value Observing'/>
<page name='UIPickerView+ObservableArray2D'/>
<page name='Trees'/>
</pages>
</playground>
36 changes: 18 additions & 18 deletions Sources/Bond/AppKit/NSOutlineView+Changeset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ import ReactiveKit
open class OutlineViewBinder<Changeset: TreeChangesetProtocol>: NSObject, NSOutlineViewDataSource where Changeset.Collection: TreeArrayProtocol {

// NSOutlineView display a tree data structure that does not have a single root,
// thus we need to bind TreeArray instead of TreeNode. TreeArray is an array of TreeNodes that implements TreeNodeProtocol.
// thus we need to bind TreeArray instead of TreeNode. TreeArray is an array of TreeNodes that implements TreeProtocol.
//
// "Items" of type `Any` that NSOutlineView asks for in the data source methods are tree nodes themselves, in our case `TreeNode`.
// Note that we can't use value types here because NSOutlineView is a ObjC type so we need to use TreeArray.Object and TreeNode.Object variants.
//
// What NSOutlineView calls "object value of an item" is the value associated with a tree node: `node.value`.

public typealias IsItemExpandable = (Changeset.Collection.ChildNode, NSOutlineView) -> Bool
public typealias IsItemExpandable = (Changeset.Collection.Children.Element, NSOutlineView) -> Bool

public var isItemExpandable: IsItemExpandable? = nil

/// Local clone of the bound data tree wrapped into a class based tree type.
public var rootNode = ObjectTreeArray<Changeset.Collection.ChildNode>()
public var rootNode = ObjectTreeArray<Changeset.Collection.Children.Element>()

public var changeset: Changeset? = nil {
didSet {
Expand Down Expand Up @@ -70,34 +70,34 @@ open class OutlineViewBinder<Changeset: TreeChangesetProtocol>: NSObject, NSOutl
open var itemInsertionAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp]
open var itemDeletionAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp]

public func item(at indexPath: IndexPath) -> ObjectTreeNode<Changeset.Collection.ChildNode> {
public func item(at indexPath: IndexPath) -> ObjectTreeNode<Changeset.Collection.Children.Element> {
return rootNode[indexPath]
}

public func treeNode(forItem item: Any) -> Changeset.Collection.ChildNode? {
return (item as? ObjectTreeNode<Changeset.Collection.ChildNode>)?.value
public func treeNode(forItem item: Any) -> Changeset.Collection.Children.Element? {
return (item as? ObjectTreeNode<Changeset.Collection.Children.Element>)?.value
}

// MARK: - NSOutlineViewDataSource

@objc(outlineView:isItemExpandable:)
open func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
guard let item = item as? ObjectTreeNode<Changeset.Collection.ChildNode> else { return false }
return isItemExpandable?(item.value, outlineView) ?? item.isEmpty == false
guard let item = item as? ObjectTreeNode<Changeset.Collection.Children.Element> else { return false }
return isItemExpandable?(item.value, outlineView) ?? item.children.isEmpty == false
}

@objc(outlineView:numberOfChildrenOfItem:)
open func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let item = item as? ObjectTreeNode<Changeset.Collection.ChildNode> {
return item.count
if let item = item as? ObjectTreeNode<Changeset.Collection.Children.Element> {
return item.children.count
} else {
return rootNode.count
return rootNode.children.count
}
}

@objc(outlineView:child:ofItem:)
open func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let item = item as? ObjectTreeNode<Changeset.Collection.ChildNode> {
if let item = item as? ObjectTreeNode<Changeset.Collection.Children.Element> {
return item[[index]]
} else {
return rootNode[[index]]
Expand All @@ -106,7 +106,7 @@ open class OutlineViewBinder<Changeset: TreeChangesetProtocol>: NSObject, NSOutl

@objc(outlineView:objectValueForTableColumn:byItem:)
open func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
return (item as? ObjectTreeNode<Changeset.Collection.ChildNode>)?.value.value ?? nil
return (item as? ObjectTreeNode<Changeset.Collection.Children.Element>)?.value.value ?? nil
}

open func applyChangeset(_ changeset: Changeset) {
Expand All @@ -122,7 +122,7 @@ open class OutlineViewBinder<Changeset: TreeChangesetProtocol>: NSObject, NSOutl
}
}

open func applyOperation(_ operation: OrderedCollectionOperation<Changeset.Collection.ChildNode, IndexPath>) {
open func applyOperation(_ operation: OrderedCollectionOperation<Changeset.Collection.Children.Element, IndexPath>) {
guard let outlineView = outlineView else { return }

switch operation {
Expand All @@ -147,7 +147,7 @@ open class OutlineViewBinder<Changeset: TreeChangesetProtocol>: NSObject, NSOutl
}
}

public func parentNode(forPath path: IndexPath) -> ObjectTreeNode<Changeset.Collection.ChildNode>? {
public func parentNode(forPath path: IndexPath) -> ObjectTreeNode<Changeset.Collection.Children.Element>? {
guard path.count > 1 else {
return nil
}
Expand All @@ -164,13 +164,13 @@ open class OutlineViewBinder<Changeset: TreeChangesetProtocol>: NSObject, NSOutl
}
}

private func clone(_ array: Changeset.Collection) -> ObjectTreeArray<Changeset.Collection.ChildNode> {
private func clone(_ array: Changeset.Collection) -> ObjectTreeArray<Changeset.Collection.Children.Element> {
return ObjectTreeArray(array.children.map { clone($0) })
}

private func clone(_ node: Changeset.Collection.ChildNode) -> ObjectTreeNode<Changeset.Collection.ChildNode> {
private func clone(_ node: Changeset.Collection.Children.Element) -> ObjectTreeNode<Changeset.Collection.Children.Element> {
var newNode = ObjectTreeNode(node)
for index in node.indices {
for index in node.bfsView.indices.dropFirst() {
newNode.insert(ObjectTreeNode(node[index]), at: index)
}
return newNode
Expand Down
4 changes: 2 additions & 2 deletions Sources/Bond/Data Sources/OutlineChangesetConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ extension TreeChangeset: OutlineChangesetConvertible where Collection: TreeArray

extension TreeArray: OutlineChangesetConvertible {

public var asTreeArrayChangeset: TreeChangeset<TreeArray<ChildValue>> {
public var asTreeArrayChangeset: TreeChangeset<TreeArray<Value>> {
return TreeChangeset(collection: self, patch: [])
}
}

extension ObjectTreeArray: OutlineChangesetConvertible {

public var asTreeArrayChangeset: TreeChangeset<ObjectTreeArray<ChildValue>> {
public var asTreeArrayChangeset: TreeChangeset<ObjectTreeArray<Value>> {
return TreeChangeset(collection: self, patch: [])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,18 @@ extension TreeArray: SectionedDataSourceProtocol {
}
}

extension TreeArray: QueryableSectionedDataSourceProtocol where ChildValue: Array2DElementProtocol {
extension TreeArray: QueryableSectionedDataSourceProtocol where Value: Array2DElementProtocol {

public typealias Item = ChildValue.Item
public typealias Item = Value.Item

public func item(at indexPath: IndexPath) -> ChildValue.Item {
public func item(at indexPath: IndexPath) -> Value.Item {
return self[indexPath].value.asArray2DElement.item!
}
}

extension TreeArray: SectionedDataSourceChangesetConvertible {

public var asSectionedDataSourceChangeset: TreeChangeset<TreeArray<ChildValue>> {
public var asSectionedDataSourceChangeset: TreeChangeset<TreeArray<Value>> {
return TreeChangeset(collection: self, patch: [])
}
}
Expand Down
40 changes: 20 additions & 20 deletions Sources/Bond/Data Structures/Array2D.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,53 +80,53 @@ public protocol Array2DElementProtocol {
var asArray2DElement: Array2DElement<Section, Item> { get }
}

extension TreeArray where ChildValue: Array2DElementProtocol {
extension TreeArray where Value: Array2DElementProtocol {

public init(sectionsWithItems: [(ChildValue.Section, [ChildValue.Item])]) {
self.init(sectionsWithItems.map { TreeNode(ChildValue(section: $0.0), $0.1.map { TreeNode(ChildValue(item: $0)) }) })
public init(sectionsWithItems: [(Value.Section, [Value.Item])]) {
self.init(sectionsWithItems.map { TreeNode(Value(section: $0.0), $0.1.map { TreeNode(Value(item: $0)) }) })
}

public subscript(itemAt indexPath: IndexPath) -> ChildValue.Item {
public subscript(itemAt indexPath: IndexPath) -> Value.Item {
get {
return self[indexPath].value.item!
}
set {
self[indexPath].value = ChildValue(item: newValue)
self[indexPath].value = Value(item: newValue)
}
}

public subscript(sectionAt index: Int) -> ChildValue.Section {
public subscript(sectionAt index: Int) -> Value.Section {
get {
return self[[index]].value.section!
}
set {
self[[index]].value = ChildValue(section: newValue)
self[[index]].value = Value(section: newValue)
}
}

/// Append new section at the end of the 2D array.
public mutating func appendSection(_ section: ChildValue.Section) {
append(TreeNode(ChildValue(section: section)))
public mutating func appendSection(_ section: Value.Section) {
append(TreeNode(Value(section: section)))
}

/// Append `item` to the section `section` of the array.
public mutating func appendItem(_ item: ChildValue.Item, toSectionAt sectionIndex: Int) {
public mutating func appendItem(_ item: Value.Item, toSectionAt sectionIndex: Int) {
insert(item: item, at: [sectionIndex, self[[sectionIndex]].children.count])
}

/// Insert section at `index` with `items`.
public mutating func insert(section: ChildValue.Section, at index: Int) {
insert(TreeNode(ChildValue(section: section)), at: [index])
public mutating func insert(section: Value.Section, at index: Int) {
insert(TreeNode(Value(section: section)), at: [index])
}

/// Insert `item` at `indexPath`.
public mutating func insert(item: ChildValue.Item, at indexPath: IndexPath) {
insert(TreeNode(ChildValue(item: item)), at: indexPath)
public mutating func insert(item: Value.Item, at indexPath: IndexPath) {
insert(TreeNode(Value(item: item)), at: indexPath)
}

/// Insert `items` at index path `indexPath`.
public mutating func insert(contentsOf items: [ChildValue.Item], at indexPath: IndexPath) {
insert(contentsOf: items.map { TreeNode(ChildValue(item: $0)) }, at: indexPath)
public mutating func insert(contentsOf items: [Value.Item], at indexPath: IndexPath) {
insert(contentsOf: items.map { TreeNode(Value(item: $0)) }, at: indexPath)
}

/// Move the section at index `fromIndex` to index `toIndex`.
Expand All @@ -141,20 +141,20 @@ extension TreeArray where ChildValue: Array2DElementProtocol {

/// Remove and return the section at `index`.
@discardableResult
public mutating func removeSection(at index: Int) -> ChildValue.Section {
public mutating func removeSection(at index: Int) -> Value.Section {
return remove(at: [index]).value.section!
}

/// Remove and return the item at `indexPath`.
@discardableResult
public mutating func removeItem(at indexPath: IndexPath) -> ChildValue.Item {
public mutating func removeItem(at indexPath: IndexPath) -> Value.Item {
return remove(at: indexPath).value.item!
}

/// Remove all items from the array. Keep empty sections.
public mutating func removeAllItems() {
for index in indices {
self[index].removeAll()
for index in 0..<children.count {
children[index].removeAll()
}
}

Expand Down
Loading

0 comments on commit 1533ede

Please sign in to comment.