Add Expandable protocol to expand/collapse cells
This commit is contained in:
parent
921e2b42b3
commit
6eaf2cf3a2
|
|
@ -25,7 +25,10 @@ public protocol ConfigurableCell {
|
|||
associatedtype CellData
|
||||
|
||||
static var reuseIdentifier: String { get }
|
||||
|
||||
@available(*, deprecated, message: "For static cells use defaultHeight, height of self-sized cells will be calculated automatically")
|
||||
static var estimatedHeight: CGFloat? { get }
|
||||
|
||||
static var defaultHeight: CGFloat? { get }
|
||||
|
||||
func configure(with _: CellData)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import UIKit
|
||||
|
||||
public protocol Expandable {
|
||||
|
||||
associatedtype ViewModelType: ExpandableCellViewModel
|
||||
|
||||
var viewModel: ViewModelType? { get }
|
||||
|
||||
func configureAppearance(isCollapsed: Bool)
|
||||
|
||||
}
|
||||
|
||||
extension Expandable where Self: UITableViewCell & ConfigurableCell {
|
||||
|
||||
public func toggleState(animated: Bool = true,
|
||||
animationDuration: TimeInterval = 0.3) {
|
||||
|
||||
guard let tableView = tableView,
|
||||
let viewModel = viewModel else {
|
||||
return
|
||||
}
|
||||
|
||||
let contentOffset = tableView.contentOffset
|
||||
|
||||
if animated {
|
||||
UIView.animate(withDuration: animationDuration,
|
||||
animations: { [weak self] in
|
||||
self?.applyChanges(isCollapsed: !viewModel.isCollapsed)
|
||||
}, completion: { _ in
|
||||
viewModel.isCollapsed.toggle()
|
||||
})
|
||||
} else {
|
||||
applyChanges(isCollapsed: !viewModel.isCollapsed)
|
||||
viewModel.isCollapsed.toggle()
|
||||
}
|
||||
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
|
||||
tableView.setContentOffset(contentOffset, animated: false)
|
||||
}
|
||||
|
||||
private func applyChanges(isCollapsed: Bool) {
|
||||
configureAppearance(isCollapsed: isCollapsed)
|
||||
layoutIfNeeded()
|
||||
|
||||
if let indexPath = indexPath,
|
||||
let tableDirector = (tableView?.delegate as? TableDirector),
|
||||
let cellHeightCalculator = tableDirector.rowHeightCalculator as? ExpandableCellHeightCalculator {
|
||||
cellHeightCalculator.updateCached(height: height, for: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import UIKit
|
||||
|
||||
public final class ExpandableCellHeightCalculator: RowHeightCalculator {
|
||||
|
||||
private(set) weak var tableView: UITableView?
|
||||
|
||||
private var prototypes = [String: UITableViewCell]()
|
||||
|
||||
private var cachedHeights = [IndexPath: CGFloat]()
|
||||
|
||||
public init(tableView: UITableView?) {
|
||||
self.tableView = tableView
|
||||
}
|
||||
|
||||
public func updateCached(height: CGFloat, for indexPath: IndexPath) {
|
||||
cachedHeights[indexPath] = height
|
||||
}
|
||||
|
||||
public func height(forRow row: Row, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
guard let tableView = tableView else {
|
||||
return 0
|
||||
}
|
||||
|
||||
if let height = cachedHeights[indexPath] {
|
||||
return height
|
||||
}
|
||||
|
||||
var prototypeCell = prototypes[row.reuseIdentifier]
|
||||
if prototypeCell == nil {
|
||||
prototypeCell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier)
|
||||
prototypes[row.reuseIdentifier] = prototypeCell
|
||||
}
|
||||
|
||||
guard let cell = prototypeCell else {
|
||||
return 0
|
||||
}
|
||||
|
||||
row.configure(cell)
|
||||
cell.layoutIfNeeded()
|
||||
|
||||
let height = cell.height
|
||||
cachedHeights[indexPath] = height
|
||||
return height
|
||||
}
|
||||
|
||||
public func estimatedHeight(forRow row: Row, at indexPath: IndexPath) -> CGFloat {
|
||||
return height(forRow: row, at: indexPath)
|
||||
}
|
||||
|
||||
public func invalidate() {
|
||||
cachedHeights.removeAll()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
public protocol ExpandableCellViewModel: class {
|
||||
|
||||
var isCollapsed: Bool { get set }
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import UIKit
|
||||
|
||||
extension UITableViewCell {
|
||||
|
||||
public var tableView: UITableView? {
|
||||
var view = superview
|
||||
|
||||
while view != nil && !(view is UITableView) {
|
||||
view = view?.superview
|
||||
}
|
||||
|
||||
return view as? UITableView
|
||||
}
|
||||
|
||||
public var indexPath: IndexPath? {
|
||||
guard let indexPath = tableView?.indexPath(for: self) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return indexPath
|
||||
}
|
||||
|
||||
public var height: CGFloat {
|
||||
return contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,6 +7,10 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */; };
|
||||
3201E78621BE9E25001DF9E7 /* UITableViewCell+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */; };
|
||||
3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78721BE9EB2001DF9E7 /* Expandable.swift */; };
|
||||
3201E78A21BE9ED4001DF9E7 /* ExpandableCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */; };
|
||||
320C5280218EB9A7004EAD1C /* AccurateCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320C527F218EB9A7004EAD1C /* AccurateCellHeightCalculator.swift */; };
|
||||
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */; };
|
||||
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E858571DB153F500A9AA55 /* TableKit.swift */; };
|
||||
|
|
@ -33,6 +37,10 @@
|
|||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellHeightCalculator.swift; sourceTree = "<group>"; };
|
||||
3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+Extensions.swift"; sourceTree = "<group>"; };
|
||||
3201E78721BE9EB2001DF9E7 /* Expandable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expandable.swift; sourceTree = "<group>"; };
|
||||
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellViewModel.swift; sourceTree = "<group>"; };
|
||||
320C527F218EB9A7004EAD1C /* AccurateCellHeightCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccurateCellHeightCalculator.swift; sourceTree = "<group>"; };
|
||||
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellRegisterer.swift; sourceTree = "<group>"; };
|
||||
50E858571DB153F500A9AA55 /* TableKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableKit.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -92,17 +100,21 @@
|
|||
DA9EA7A51D0EC2B90021F650 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
50E858571DB153F500A9AA55 /* TableKit.swift */,
|
||||
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */,
|
||||
320C527F218EB9A7004EAD1C /* AccurateCellHeightCalculator.swift */,
|
||||
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */,
|
||||
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */,
|
||||
DA9EA7A81D0EC2C90021F650 /* Operators.swift */,
|
||||
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */,
|
||||
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */,
|
||||
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */,
|
||||
50E858571DB153F500A9AA55 /* TableKit.swift */,
|
||||
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */,
|
||||
DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */,
|
||||
DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */,
|
||||
DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */,
|
||||
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */,
|
||||
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */,
|
||||
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */,
|
||||
DA9EA7A81D0EC2C90021F650 /* Operators.swift */,
|
||||
320C527F218EB9A7004EAD1C /* AccurateCellHeightCalculator.swift */,
|
||||
3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */,
|
||||
3201E78721BE9EB2001DF9E7 /* Expandable.swift */,
|
||||
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -233,14 +245,18 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3201E78A21BE9ED4001DF9E7 /* ExpandableCellViewModel.swift in Sources */,
|
||||
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */,
|
||||
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */,
|
||||
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */,
|
||||
3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */,
|
||||
320C5280218EB9A7004EAD1C /* AccurateCellHeightCalculator.swift in Sources */,
|
||||
DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */,
|
||||
DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */,
|
||||
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */,
|
||||
DA9EA7B51D0EC2C90021F650 /* TableRowAction.swift in Sources */,
|
||||
DA9EA7B21D0EC2C90021F650 /* TableCellAction.swift in Sources */,
|
||||
3201E78621BE9E25001DF9E7 /* UITableViewCell+Extensions.swift in Sources */,
|
||||
DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */,
|
||||
DA9EA7B41D0EC2C90021F650 /* TableRow.swift in Sources */,
|
||||
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */,
|
||||
|
|
|
|||
Loading…
Reference in New Issue