Skip to content

Commit

Permalink
Merge pull request #209 from Shopify/jk_ftd2
Browse files Browse the repository at this point in the history
Provide first-class support for UICollectionView
  • Loading branch information
jasonkemp9099 authored Nov 4, 2021
2 parents 2c83e4b + 5c6a230 commit ba4052d
Show file tree
Hide file tree
Showing 30 changed files with 2,440 additions and 325 deletions.
91 changes: 91 additions & 0 deletions Sources/FunctionalTableData/AnyHashableConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// AnyHashableConfig.swift
// FunctionalTableData
//
// Created by Jason Kemp on 2021-10-27.
//

import UIKit

public struct AnyHashableConfig: Hashable, HashableCellConfigType {
public static func ==(lhs: AnyHashableConfig, rhs: AnyHashableConfig) -> Bool {
return lhs.hashable == rhs.hashable
}

private var base: CellConfigType
public let hashable: AnyHashable

public func hash(into hasher: inout Hasher) {
hasher.combine(hashable)
}

public init(_ base: CellConfigType, sectionKey: String) {
self.base = base
if let hashableConfig = base as? HashableCellConfigType {
self.hashable = hashableConfig.hashable
} else {
self.hashable = AnyHashable(ItemPath(sectionKey: sectionKey, itemKey: base.key))
}
}

public init(_ base: HashableCellConfigType) {
self.base = base
self.hashable = base.hashable
}

public var key: String {
return base.key
}

public var style: CellStyle? {
get { return base.style }
set { base.style = newValue }
}

public var actions: CellActions {
get { return base.actions }
set { base.actions = newValue }
}

public var accessibility: Accessibility {
get { return base.accessibility }
set { base.accessibility = newValue }
}

public func dequeueCell(from tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
return base.dequeueCell(from: tableView, at: indexPath)
}

public func dequeueCell(from collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell {
return base.dequeueCell(from: collectionView, at: indexPath)
}

public func update(cell: UITableViewCell, in tableView: UITableView) {
base.update(cell: cell, in: tableView)
}

public func update(cell: UICollectionViewCell, in collectionView: UICollectionView) {
base.update(cell: cell, in: collectionView)
}

public func isEqual(_ other: CellConfigType) -> Bool {
guard let other = other as? AnyHashableConfig else { return false }
return base.isEqual(other.base)
}

public func isSameKind(as other: CellConfigType) -> Bool {
return base.isSameKind(as: other)
}

public func debugInfo() -> [String : Any] {
return base.debugInfo()
}

public func register(with tableView: UITableView) {
base.register(with: tableView)
}

public func register(with collectionView: UICollectionView) {
base.register(with: collectionView)
}
}
79 changes: 0 additions & 79 deletions Sources/FunctionalTableData/CellConfigType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,82 +63,3 @@ public extension CellConfigType {
return type(of: self) == type(of: other)
}
}

public struct AnyCellConfigType: CellConfigType, Hashable {

public static func ==(lhs: AnyCellConfigType, rhs: AnyCellConfigType) -> Bool {
return lhs.sectionKey == rhs.sectionKey && lhs.key == rhs.key
}

public var key: String {
return base.key
}

public var style: CellStyle? {
get { return base.style }
set { base.style = newValue }
}

public var actions: CellActions {
get { return base.actions }
set { base.actions = newValue }
}

public var accessibility: Accessibility {
get { return base.accessibility }
set { base.accessibility = newValue }
}

public var base: CellConfigType
private let sectionKey: String

init(_ base: CellConfigType, sectionKey: String) {
self.base = base
self.sectionKey = sectionKey
}

public init(_ base: CellConfigType) {
self.init(base, sectionKey: "")
}

public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(sectionKey)
hasher.combine(style)
}

public func dequeueCell(from tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
return base.dequeueCell(from: tableView, at: indexPath)
}

public func update(cell: UITableViewCell, in tableView: UITableView) {
base.update(cell: cell, in: tableView)
}

public func update(cell: UICollectionViewCell, in collectionView: UICollectionView) {
base.update(cell: cell, in: collectionView)
}

public func isEqual(_ other: CellConfigType) -> Bool {
guard let other = other as? AnyCellConfigType else { return false }
return sectionKey == other.sectionKey && key == other.key
}

public func isSameKind(as other: CellConfigType) -> Bool {
return base.isSameKind(as: other)
}

public func debugInfo() -> [String : Any] {
return base.debugInfo()
}

public func register(with tableView: UITableView) {
base.register(with: tableView)
}

public func register(with collectionView: UICollectionView) {
base.register(with: collectionView)
}


}
3 changes: 1 addition & 2 deletions Sources/FunctionalTableData/CellStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public struct CellStyle {
public var separatorColor: UIColor?
/// Whether the cell is highlighted or not.
///
/// Supported by `UITableView` only.
public var highlight: Bool?
/// The type of standard accessory control used by a cell.
/// You use these constants when setting the value of the [accessoryType](apple-reference-documentation://hspQPOCGHb) property.
Expand Down Expand Up @@ -162,7 +161,7 @@ public struct CellStyle {
}

cell.selectedBackgroundView = nil
if let selectionColor = selectionColor {
if let highlight = highlight, highlight, let selectionColor = selectionColor {
let selectedBackgroundView = UIView()
selectedBackgroundView.backgroundColor = selectionColor
cell.selectedBackgroundView = selectedBackgroundView
Expand Down
76 changes: 76 additions & 0 deletions Sources/FunctionalTableData/CollectionSection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// CollectionSection.swift
// FunctionalTableData
//
// Created by Jason Kemp on 2021-10-05.
// Copyright © 2021 Shopify. All rights reserved.

import UIKit

public typealias CellPreparer = (UICollectionViewCell, UICollectionView, IndexPath) -> Void

public protocol CollectionSection {
var key: String { get }

var items: [CellConfigType] { get set }

var supplementaries: [CollectionSupplementaryItemConfig] { get set }

/// Callback executed when an item is manually moved by the user. It specifies the before and after index position.
var didMoveRow: ((_ from: Int, _ to: Int) -> Void)? { get }

func prepareCell(_ cell: UICollectionViewCell, in collectionView: UICollectionView, for indexPath: IndexPath)
}

public protocol HashableCellConfigType: CellConfigType {
var hashable: AnyHashable { get }
}

public extension CollectionSection {
func supplementaryConfig(ofKind kind: ReusableKind) -> CollectionSupplementaryItemConfig? {
return supplementaries.first(where: { $0.kind == kind })
}

var header: CollectionSupplementaryItemConfig? {
return supplementaries.first(where: { $0.kind == .header })
}

var footer: CollectionSupplementaryItemConfig? {
return supplementaries.first(where: { $0.kind == .footer })
}
}

public struct SimpleCollectionSection: CollectionSection, Hashable {
public static func ==(lhs: SimpleCollectionSection, rhs: SimpleCollectionSection) -> Bool {
return lhs.key == rhs.key
}

public let key: String
public var items: [CellConfigType]
public var supplementaries: [CollectionSupplementaryItemConfig]

public init(key: String,
items: [CellConfigType],
supplementaries: [CollectionSupplementaryItemConfig] = [],
didMoveRow: ((Int, Int) -> Void)? = nil,
cellPreparer: CellPreparer? = nil) {
self.key = key
self.items = items
self.supplementaries = supplementaries
self.didMoveRow = didMoveRow
self.cellPreparer = cellPreparer
}

public func hash(into hasher: inout Hasher) {
hasher.combine(key)
}

/// Callback executed when an item is manually moved by the user. It specifies the before and after index position.
public let didMoveRow: ((_ from: Int, _ to: Int) -> Void)?

public var cellPreparer: CellPreparer?

public func prepareCell(_ cell: UICollectionViewCell, in collectionView: UICollectionView, for indexPath: IndexPath) {
cellPreparer?(cell, collectionView, indexPath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// AnyCollectionSection.swift
// FunctionalTableData
//
// Created by Jason Kemp on 2021-10-28.
//

import UIKit

struct AnyCollectionSection: Hashable {
public static func ==(lhs: AnyCollectionSection, rhs: AnyCollectionSection) -> Bool {
return lhs.key == rhs.key
}

private var impl: CollectionSection

public var key: String { impl.key }
public var items: [HashableCellConfigType]
public var supplementaries: [CollectionSupplementaryItemConfig] {
get { impl.supplementaries }
set { impl.supplementaries = newValue }
}

public init(_ section: CollectionSection) {
items = section.items.map { AnyHashableConfig($0, sectionKey: section.key) }
impl = section
}

public func hash(into hasher: inout Hasher) {
hasher.combine(key)
}

/// Callback executed when a item is manually moved by the user. It specifies the before and after index position.
public var didMoveRow: ((_ from: Int, _ to: Int) -> Void)? { impl.didMoveRow }

public func prepareCell(_ cell: UICollectionViewCell, in collectionView: UICollectionView, for indexPath: IndexPath) {
impl.prepareCell(cell, in: collectionView, for: indexPath)
}
}
70 changes: 70 additions & 0 deletions Sources/FunctionalTableData/CollectionView/CellConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// CellConfig.swift
//
//
// Created by Jason Kemp on 2021-10-19.
//

import UIKit

public struct CellConfig<View, State>: HashableCellConfigType where View: UIView & ConfigurableView, State: Hashable, View.State == State {
public typealias CollectionCellType = ConfigurableCollectionCell<View, State>

public let key: String
public var hashable: AnyHashable { return AnyHashable(state) }
public var style: CellStyle?
public var actions: CellActions
public var accessibility: Accessibility
public let state: State

public init(key: String,
style: CellStyle? = CellStyle(backgroundColor: nil),
actions: CellActions = CellActions(),
accessibility: Accessibility = Accessibility(),
state: State) {
self.key = key
self.style = style
self.actions = actions
self.accessibility = accessibility
self.state = state
}

public func isEqual(_ other: CellConfigType) -> Bool {
guard let other = other as? CellConfig<View, State> else {
return false
}
return state == other.state && accessibility == other.accessibility
}

public func debugInfo() -> [String : Any] {
return ["key": key, "type": String(describing: type(of: self))]
}

// MARK: - TableItemConfigType
public func register(with tableView: UITableView) {
// intentionally blank, intended use for CellConfig is for UICollectionView
}

public func dequeueCell(from tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}

public func update(cell: UITableViewCell, in tableView: UITableView) {
// intentionally blank, intended use for CellConfig is for UICollectionView
}

// MARK: - CollectionItemConfigType

public func register(with collectionView: UICollectionView) {
collectionView.registerReusableCell(CollectionCellType.self)
}

public func dequeueCell(from collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(CollectionCellType.self, indexPath: indexPath)
}

public func update(cell: UICollectionViewCell, in collectionView: UICollectionView) {
guard let cell = cell as? CollectionCellType else { return }
cell.configure(state)
}
}
Loading

0 comments on commit ba4052d

Please sign in to comment.