Skip to content

Commit

Permalink
Refactor Array2D intro a simple struct.
Browse files Browse the repository at this point in the history
  • Loading branch information
srdanrasic committed Mar 8, 2019
1 parent 4c27eb6 commit 807f69d
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class CustomBinder<Changeset: SectionedDataSourceChangeset>: TableViewBinderData

// Important: Annotate UITableViewDataSource methods with `@objc` in the subclass, otherwise UIKit will not see your method!
@objc func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return changeset?.collection[sectionAt: section]
return changeset?.collection[sectionAt: section].metadata
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,18 @@ extension TreeArray: SectionedDataSourceProtocol {
}
}

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

public typealias Item = Value.Item
public var numberOfSections: Int {
return sections.count
}

public func numberOfItems(inSection section: Int) -> Int {
return sections[section].items.count
}

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

Expand Down
197 changes: 124 additions & 73 deletions Sources/Bond/Data Structures/Array2D.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,142 +24,193 @@

import Foundation

/// Array2D is a 3-level tree of nodes with `Array2DElement<Section, Item>` values.
/// First level represents the root node and contains no value. Second level represents
/// sections and contains `Array2DElement.section` values, while the third level represents
/// items and contains `Array2DElement.item` values.
///
/// Signals that emit this kind of a tree can be bound to a table or collection view.
public typealias Array2D<Section, Item> = TreeArray<Array2DElement<Section, Item>>

/// A section or an item in a 2D array.
public enum Array2DElement<Section, Item>: Array2DElementProtocol {

/// Section with the associated value of type `Section`.
case section(Section)
public protocol Array2DProtocol: RangeReplaceableTreeProtocol where Children == [Array2D<SectionMetadata, Item>.Element] {
associatedtype SectionMetadata
associatedtype Item
}

/// Item with the associated value of type `Item`.
case item(Item)
/// A data structure whose items are grouped into section.
/// Array2D can be used as an underlying data structure for UITableView or UICollectionView data source.
public struct Array2D<SectionMetadata, Item>: Array2DProtocol {

public init(item: Item) {
self = .item(item)
}
/// Represents a single section of Array2D.
public struct Section {

public init(section: Section) {
self = .section(section)
}
/// Section metadata, e.g. section title.
public var metadata: SectionMetadata

public var section: Section? {
if case .section(let section) = self {
return section
} else {
return nil
}
/// Items contained in the section.
public var items: [Item]
}

public var item: Item? {
if case .item(let item) = self {
return item
} else {
return nil
}
}
/// All sections of Array2D.
public var sections: [Section]

public var asArray2DElement: Array2DElement<Section, Item> {
return self
/// Create a new Array2D with the given sections.
public init(sections: [Section] = []) {
self.sections = sections
}
}

public protocol Array2DElementProtocol {
associatedtype Section
associatedtype Item
init(section: Section)
init(item: Item)
var section: Section? { get }
var item: Item? { get }
var asArray2DElement: Array2DElement<Section, Item> { get }
}
// MARK: Convenience methods

extension TreeArray where Value: Array2DElementProtocol {
extension Array2D {

public init(sectionsWithItems: [(Value.Section, [Value.Item])]) {
self.init(sectionsWithItems.map { TreeNode(Value(section: $0.0), $0.1.map { TreeNode(Value(item: $0)) }) })
/// Create a new Array2D from the given list of section metadata and respective items.
/// Each SectionMetadata corresponds to one section populated with the given items.
public init(sectionsWithItems: [(SectionMetadata, [Item])]) {
self.init(sections: sectionsWithItems.map { Section(metadata: $0.0, items: $0.1) })
}

public subscript(itemAt indexPath: IndexPath) -> Value.Item {
/// Access or mutate an item at the given index path.
public subscript(itemAt indexPath: IndexPath) -> Item {
get {
return self[childAt: indexPath].value.item!
return sections[indexPath.section].items[indexPath.item]
}
set {
self[childAt: indexPath].value = Value(item: newValue)
sections[indexPath.section].items[indexPath.item] = newValue
}
}

public subscript(sectionAt index: Int) -> Value.Section {
/// Access or mutate a section at the given index.
public subscript(sectionAt index: Int) -> Section {
get {
return self[childAt: [index]].value.section!
return sections[index]
}
set {
self[childAt: [index]].value = Value(section: newValue)
sections[index] = newValue
}
}

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

/// Append new section at the end of the 2D array.
public mutating func appendSection(_ metadata: SectionMetadata) {
sections.append(Section(metadata: metadata, items: []))
}


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

/// Insert section at `index` with `items`.
public mutating func insert(section: Section, at index: Int) {
sections.insert(section, at: index)
}

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

/// Insert `item` at `indexPath`.
public mutating func insert(item: Value.Item, at indexPath: IndexPath) {
insert(TreeNode(Value(item: item)), at: indexPath)
public mutating func insert(item: Item, at indexPath: IndexPath) {
sections[indexPath.section].items.insert(item, at: indexPath.item)
}

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

/// Move the section at index `fromIndex` to index `toIndex`.
public mutating func moveSection(from fromIndex: Int, to toIndex: Int) {
move(from: [fromIndex], to: [toIndex])
sections.move(from: fromIndex, to: toIndex)
}

/// Move the item at `fromIndexPath` to `toIndexPath`.
public mutating func moveItem(from fromIndexPath: IndexPath, to toIndexPath: IndexPath) {
move(from: fromIndexPath, to: toIndexPath)
let item = sections[fromIndexPath.section].items.remove(at: fromIndexPath.item)
sections[toIndexPath.section].items.insert(item, at: toIndexPath.item)
}

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

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

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

/// Remove all items and sections from the array (aka `removeAll`).
public mutating func removeAllItemsAndSections() {
removeAll()
sections.removeAll()
}
}

// MARK: TreeProtocol conformance

extension Array2D {

/// A type that represents a node of Array2D tree - either a section or an item.
public enum Element: RangeReplaceableTreeProtocol {
case section(Section)
case item(Item)

/// Child nodes of the element. In case of a section, children are its items. In case of an item, empty array.
public var children: [Element] {
get {
switch self {
case .section(let section):
return section.items.map { Element.item($0) }
case .item:
return []
}
}
set {
switch self {
case .section(let section):
self = .section(Section(metadata: section.metadata, items: newValue.compactMap { $0.item }))
case .item:
break
}
}
}

/// A section if the node represents a section node, nil otherwise.
public var section: Section? {
switch self {
case .section(let section):
return section
default:
return nil
}
}

/// An item if the node represents an item node, nil otherwise.
public var item: Item? {
switch self {
case .item(let item):
return item
default:
return nil
}
}
}

/// Child nodes of the Array2D tree. First level of the tree are sections, while the second level are items.
public var children: [Element] {
get {
return sections.map { Element.section($0) }
}
set {
sections = newValue.compactMap { $0.section }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ public typealias MutableObservableDictionary<Key: Hashable, Value> = Property<Un
public typealias ObservableTree<Tree: TreeProtocol> = AnyProperty<TreeChangeset<Tree>>
public typealias MutableObservableTree<Tree: TreeProtocol> = Property<TreeChangeset<Tree>>

public typealias ObservableArray2D<SectionValue, Item> = AnyProperty<TreeChangeset<Array2D<SectionValue, Item>>>
public typealias MutableObservableArray2D<SectionValue, Item> = Property<TreeChangeset<Array2D<SectionValue, Item>>>
public typealias ObservableArray2D<SectionMetadata, Item> = AnyProperty<TreeChangeset<Array2D<SectionMetadata, Item>>>
public typealias MutableObservableArray2D<SectionMetadata, Item> = Property<TreeChangeset<Array2D<SectionMetadata, Item>>>

extension AnyProperty: ChangesetContainerProtocol where Value: ChangesetProtocol {

Expand Down Expand Up @@ -107,7 +107,7 @@ extension Property where Value: ChangesetProtocol, Value.Collection: Instantiata
}
}

extension Property where Value: ChangesetProtocol, Value.Collection: RangeReplaceableTreeProtocol, Value.Collection.Children.Element: TreeNodeWithValueProtocol, Value.Collection.Children.Element.Value: Array2DElementProtocol {
extension Property where Value: ChangesetProtocol, Value.Collection: Array2DProtocol {

/// Total number of items across all sections.
public var numberOfItemsInAllSections: Int {
Expand Down
Loading

0 comments on commit 807f69d

Please sign in to comment.