From 83de05fde7b84a25b6e7993b7e427d1efaf2c4db Mon Sep 17 00:00:00 2001 From: Ivan Zinovyev Date: Tue, 22 Jan 2019 03:23:46 +0300 Subject: [PATCH] Add Expandable States --- .../Base/BaseExpandableCellViewModel.swift | 28 ++++-- .../Cells/Base/BaseTableViewCell.swift | 33 +++++-- .../Cells/ExpandableAutolayoutCell.swift | 86 +++++++++++++++++-- .../Cells/ExpandableManualLayoutCell.swift | 27 +++--- .../Cells/ExpandablePinLayoutCell.swift | 52 ++++++----- .../AutoLayoutViewController.swift | 4 +- .../Controllers/Base/BaseViewController.swift | 12 +-- .../ManualLayoutViewController.swift | 4 +- .../Controllers/PinLayoutViewController.swift | 4 +- Podfile | 2 +- Podfile.lock | 12 +-- 11 files changed, 198 insertions(+), 66 deletions(-) diff --git a/ExpandableCellDemo-ios/Cells/Base/BaseExpandableCellViewModel.swift b/ExpandableCellDemo-ios/Cells/Base/BaseExpandableCellViewModel.swift index aca7b75..455c9c8 100644 --- a/ExpandableCellDemo-ios/Cells/Base/BaseExpandableCellViewModel.swift +++ b/ExpandableCellDemo-ios/Cells/Base/BaseExpandableCellViewModel.swift @@ -3,25 +3,41 @@ import TableKit class BaseExpandableCellViewModel: ExpandableCellViewModel { // MARK: - Properties - + let index: Int + + let width: CGFloat - let text: String = .random(length: .random(in: 50...400)) + let text: String = .random(length: .random(in: 100...400)) // MARK: - Colors let collapsedColor = UIColor.random() - let expandedColor = UIColor.random() - // MARK: - ExpandableCellViewModel - var isCollapsed = true + var expandableState: ExpandableState = .expanded + + static let oneLineState: ExpandableState = .height(value: BaseTableViewCell.collapsedHeight + 40) + + static let twoLinesState: ExpandableState = .height(value: BaseTableViewCell.collapsedHeight + 60) + + static let threeLinesState: ExpandableState = .height(value: BaseTableViewCell.collapsedHeight + 80) + var availableStates: [ExpandableState] = [ + .collapsed, + oneLineState, + twoLinesState, + threeLinesState, + .expanded + ] + // MARK: - Life Cycle - init(index: Int) { + init(index: Int, + width: CGFloat) { self.index = index + self.width = width } } diff --git a/ExpandableCellDemo-ios/Cells/Base/BaseTableViewCell.swift b/ExpandableCellDemo-ios/Cells/Base/BaseTableViewCell.swift index eee3619..3358657 100644 --- a/ExpandableCellDemo-ios/Cells/Base/BaseTableViewCell.swift +++ b/ExpandableCellDemo-ios/Cells/Base/BaseTableViewCell.swift @@ -19,6 +19,16 @@ class BaseTableViewCell: UITableViewCell { let label = UILabel() let indexLabel = UILabel() + + let collapsedStateButton = UIButton() + + let oneLineButton = UIButton() + + let twoLinesButton = UIButton() + + let threeLinesButton = UIButton() + + let expandedStateButton = UIButton() // MARK: - Properties @@ -35,18 +45,17 @@ class BaseTableViewCell: UITableViewCell { label.numberOfLines = 0 collapsedView.addSubview(indexLabel) + collapsedView.addSubview(collapsedStateButton) + collapsedView.addSubview(expandedStateButton) + collapsedView.addSubview(oneLineButton) + collapsedView.addSubview(twoLinesButton) + collapsedView.addSubview(threeLinesButton) containerView.addSubview(collapsedView) containerView.addSubview(label) contentView.addSubview(containerView) initializeView() } - - override func layoutSubviews() { - super.layoutSubviews() - - label.preferredMaxLayoutWidth = contentView.frame.width - 2 * BaseTableViewCell.textMargin - } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -55,7 +64,17 @@ class BaseTableViewCell: UITableViewCell { // MARK: - To Override func initializeView() { - + collapsedStateButton.setTitle("Collapsed", for: .normal) + oneLineButton.setTitle("One Line", for: .normal) + twoLinesButton.setTitle("Two Lines", for: .normal) + threeLinesButton.setTitle("Three Lines", for: .normal) + expandedStateButton.setTitle("Expanded", for: .normal) + + [collapsedStateButton, oneLineButton, twoLinesButton, threeLinesButton, expandedStateButton] + .forEach { button in + button.setTitleColor(.black, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 10) + } } } diff --git a/ExpandableCellDemo-ios/Cells/ExpandableAutolayoutCell.swift b/ExpandableCellDemo-ios/Cells/ExpandableAutolayoutCell.swift index ee374c0..a3ff3b9 100644 --- a/ExpandableCellDemo-ios/Cells/ExpandableAutolayoutCell.swift +++ b/ExpandableCellDemo-ios/Cells/ExpandableAutolayoutCell.swift @@ -1,14 +1,48 @@ import SnapKit import TableKit +private extension CGFloat { + + static let spaceBetweenStateButtons: CGFloat = 10 + +} + final class ExpandableAutolayoutCell: BaseTableViewCell, ConfigurableCell, Expandable { // MARK: - Init override func initializeView() { + super.initializeView() + collapsedView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(expand))) + + collapsedStateButton.addTarget(self, action: #selector(toCollapsed), for: .touchUpInside) + oneLineButton.addTarget(self, action: #selector(toOneLine), for: .touchUpInside) + twoLinesButton.addTarget(self, action: #selector(toTwoLines), for: .touchUpInside) + threeLinesButton.addTarget(self, action: #selector(toThreeLines), for: .touchUpInside) + expandedStateButton.addTarget(self, action: #selector(toExpanded), for: .touchUpInside) makeConstraints() } + + @objc func toCollapsed() { + transition(to: .collapsed) + } + + @objc func toOneLine() { + transition(to: BaseExpandableCellViewModel.oneLineState) + } + + @objc func toTwoLines() { + transition(to: BaseExpandableCellViewModel.twoLinesState) + } + + @objc func toThreeLines() { + transition(to: BaseExpandableCellViewModel.threeLinesState) + } + + @objc func toExpanded() { + transition(to: .expanded) + } // MARK: - Actions @@ -22,18 +56,18 @@ final class ExpandableAutolayoutCell: BaseTableViewCell, ConfigurableCell, Expan // MARK: - Expandable - func configureAppearance(isCollapsed: Bool) { + func configure(state: ExpandableState) { guard let viewModel = viewModel else { return } - heightConstraint?.layoutConstraints.first?.constant = isCollapsed + heightConstraint?.layoutConstraints.first?.constant = state.isCollapsed ? BaseTableViewCell.collapsedHeight - : label.frame.maxY + BaseTableViewCell.bottomMargin + : state.height ?? (label.frame.maxY + BaseTableViewCell.bottomMargin) - containerView.backgroundColor = isCollapsed ? viewModel.collapsedColor : viewModel.expandedColor + containerView.backgroundColor = state.isCollapsed ? viewModel.collapsedColor : .random() - label.alpha = isCollapsed ? 0 : 1 + label.alpha = state.isCollapsed ? 0 : 1 } // MARK: - ConfigurableCell @@ -43,6 +77,8 @@ final class ExpandableAutolayoutCell: BaseTableViewCell, ConfigurableCell, Expan label.text = viewModel.text indexLabel.text = String(viewModel.index) + + label.preferredMaxLayoutWidth = viewModel.width - 2 * BaseTableViewCell.textMargin initState() } @@ -62,6 +98,11 @@ private extension ExpandableAutolayoutCell { makeCollapsedViewConstraints() makeLabelConstraints() makeIndexLabelConstraints() + makeCollapsedStateButtonConstraints() + makeOneLineButtonConstraints() + makeTwoLinesButtonConstraints() + makeThreeLinesButtonConstraints() + makeExpandedStateButtonConstraints() } func makeContainerViewConstraints() { @@ -91,5 +132,40 @@ private extension ExpandableAutolayoutCell { make.top.leading.equalToSuperview() } } + + func makeCollapsedStateButtonConstraints() { + collapsedStateButton.snp.remakeConstraints { make in + make.firstBaseline.equalTo(indexLabel.snp.firstBaseline) + make.leading.equalTo(indexLabel.snp.trailing).offset(CGFloat.spaceBetweenStateButtons) + } + } + + func makeOneLineButtonConstraints() { + oneLineButton.snp.remakeConstraints { make in + make.firstBaseline.equalTo(indexLabel.snp.firstBaseline) + make.leading.equalTo(collapsedStateButton.snp.trailing).offset(CGFloat.spaceBetweenStateButtons) + } + } + + func makeTwoLinesButtonConstraints() { + twoLinesButton.snp.remakeConstraints { make in + make.firstBaseline.equalTo(indexLabel.snp.firstBaseline) + make.leading.equalTo(oneLineButton.snp.trailing).offset(CGFloat.spaceBetweenStateButtons) + } + } + + func makeThreeLinesButtonConstraints() { + threeLinesButton.snp.remakeConstraints { make in + make.firstBaseline.equalTo(indexLabel.snp.firstBaseline) + make.leading.equalTo(twoLinesButton.snp.trailing).offset(CGFloat.spaceBetweenStateButtons) + } + } + + func makeExpandedStateButtonConstraints() { + expandedStateButton.snp.remakeConstraints { make in + make.firstBaseline.equalTo(indexLabel.snp.firstBaseline) + make.leading.equalTo(threeLinesButton.snp.trailing).offset(CGFloat.spaceBetweenStateButtons) + } + } } diff --git a/ExpandableCellDemo-ios/Cells/ExpandableManualLayoutCell.swift b/ExpandableCellDemo-ios/Cells/ExpandableManualLayoutCell.swift index 7529517..d777604 100644 --- a/ExpandableCellDemo-ios/Cells/ExpandableManualLayoutCell.swift +++ b/ExpandableCellDemo-ios/Cells/ExpandableManualLayoutCell.swift @@ -5,6 +5,8 @@ final class ExpandableManualLayoutCell: BaseTableViewCell, ConfigurableCell, Exp // MARK: - Init override func initializeView() { + super.initializeView() + collapsedView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(expand))) } @@ -18,40 +20,45 @@ final class ExpandableManualLayoutCell: BaseTableViewCell, ConfigurableCell, Exp override func layoutSubviews() { super.layoutSubviews() + + guard let viewModel = viewModel else { + return + } collapsedView.frame = CGRect(x: 0, y: 0, - width: UIScreen.main.bounds.width, + width: viewModel.width, height: BaseTableViewCell.collapsedHeight) indexLabel.frame = CGRect(x: 0, y: 0, - width: UIScreen.main.bounds.width, + width: viewModel.width, height: 0) indexLabel.sizeToFit() label.frame = CGRect(x: BaseTableViewCell.textMargin, y: collapsedView.frame.maxY + BaseTableViewCell.textMargin, - width: UIScreen.main.bounds.width - BaseTableViewCell.textMargin * 2, + width: viewModel.width - BaseTableViewCell.textMargin * 2, height: 0) label.sizeToFit() } // MARK: - Expandable - func configureAppearance(isCollapsed: Bool) { + func configure(state: ExpandableState) { guard let viewModel = viewModel else { return } - containerView.frame = CGRect(x: 0, - y: 0, - width: UIScreen.main.bounds.width, - height: isCollapsed ? BaseTableViewCell.collapsedHeight : (label.frame.maxY + BaseTableViewCell.bottomMargin)) + let height = state.isCollapsed + ? BaseTableViewCell.collapsedHeight + : state.height ?? (label.frame.maxY + BaseTableViewCell.bottomMargin) + + containerView.frame = CGRect(x: 0, y: 0, width: viewModel.width, height: height) - containerView.backgroundColor = isCollapsed ? viewModel.collapsedColor : viewModel.expandedColor + containerView.backgroundColor = state.isCollapsed ? viewModel.collapsedColor : .random() - label.alpha = isCollapsed ? 0 : 1 + label.alpha = state.isCollapsed ? 0 : 1 } // MARK: - ConfigurableCell diff --git a/ExpandableCellDemo-ios/Cells/ExpandablePinLayoutCell.swift b/ExpandableCellDemo-ios/Cells/ExpandablePinLayoutCell.swift index f68756f..9096f9d 100644 --- a/ExpandableCellDemo-ios/Cells/ExpandablePinLayoutCell.swift +++ b/ExpandableCellDemo-ios/Cells/ExpandablePinLayoutCell.swift @@ -2,10 +2,12 @@ import TableKit import PinLayout final class ExpandablePinLayoutCell: BaseTableViewCell, ConfigurableCell, Expandable { - + // MARK: - Init override func initializeView() { + super.initializeView() + collapsedView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(expand))) } @@ -19,12 +21,22 @@ final class ExpandablePinLayoutCell: BaseTableViewCell, ConfigurableCell, Expand override func layoutSubviews() { super.layoutSubviews() - - indexLabel.pin - .left() + + guard let viewModel = viewModel else { + return + } + + containerView.pin .top() - .sizeToFit() - + .horizontally() + .width(viewModel.width) + + collapsedView.pin + .top() + .left() + .right() + .height(BaseTableViewCell.collapsedHeight) + label.pin .below(of: collapsedView) .marginTop(BaseTableViewCell.textMargin) @@ -32,29 +44,28 @@ final class ExpandablePinLayoutCell: BaseTableViewCell, ConfigurableCell, Expand .right(BaseTableViewCell.textMargin) .sizeToFit(.width) - collapsedView.pin - .top() + indexLabel.pin .left() - .right() - .height(BaseTableViewCell.collapsedHeight) + .top() + .sizeToFit() } // MARK: - Expandable - func configureAppearance(isCollapsed: Bool) { + func configure(state: ExpandableState) { guard let viewModel = viewModel else { return } + + let height = state.isCollapsed + ? BaseTableViewCell.collapsedHeight + : state.height ?? (label.frame.maxY + BaseTableViewCell.bottomMargin) + + containerView.pin.height(height) + + containerView.backgroundColor = state.isCollapsed ? viewModel.collapsedColor : .random() - containerView.pin - .top() - .left() - .right() - .height(isCollapsed ? BaseTableViewCell.collapsedHeight : (label.frame.maxY + BaseTableViewCell.bottomMargin)) - - containerView.backgroundColor = isCollapsed ? viewModel.collapsedColor : viewModel.expandedColor - - label.alpha = isCollapsed ? 0 : 1 + label.alpha = state.isCollapsed ? 0 : 1 } // MARK: - ConfigurableCell @@ -63,6 +74,7 @@ final class ExpandablePinLayoutCell: BaseTableViewCell, ConfigurableCell, Expand self.viewModel = viewModel label.text = viewModel.text + indexLabel.text = String(viewModel.index) initState() diff --git a/ExpandableCellDemo-ios/Controllers/AutoLayoutViewController.swift b/ExpandableCellDemo-ios/Controllers/AutoLayoutViewController.swift index cfa3f1e..bebd287 100644 --- a/ExpandableCellDemo-ios/Controllers/AutoLayoutViewController.swift +++ b/ExpandableCellDemo-ios/Controllers/AutoLayoutViewController.swift @@ -6,10 +6,10 @@ class AutoLayoutViewController: BaseViewController { return "Auto Layout" } - override var rows: [Row] { + override func rows(width: CGFloat) -> [Row] { return Array(0...100) .map { - TableRow(item: BaseExpandableCellViewModel(index: $0)) + TableRow(item: BaseExpandableCellViewModel(index: $0, width: width)) } } diff --git a/ExpandableCellDemo-ios/Controllers/Base/BaseViewController.swift b/ExpandableCellDemo-ios/Controllers/Base/BaseViewController.swift index 6a0b073..3e40dea 100644 --- a/ExpandableCellDemo-ios/Controllers/Base/BaseViewController.swift +++ b/ExpandableCellDemo-ios/Controllers/Base/BaseViewController.swift @@ -13,11 +13,12 @@ class BaseViewController: UIViewController { return "" } - var rows: [Row] { + func rows(width: CGFloat) -> [Row] { return [] } - private lazy var tableDirector = TableDirector(tableView: tableView) + private lazy var tableDirector = TableDirector(tableView: tableView, + cellHeightCalculator: ExpandableCellHeightCalculator(tableView: tableView)) // MARK: - Life Cycle @@ -30,13 +31,14 @@ class BaseViewController: UIViewController { tableView.separatorStyle = .none navigationItem.title = layoutType + let width = view.frame.width DispatchQueue.global(qos: .userInitiated).async { [weak self] in - self?.loadRows() + self?.loadRows(width: width) } } - func loadRows() { - let section = TableSection(onlyRows: rows) + func loadRows(width: CGFloat) { + let section = TableSection(onlyRows: rows(width: width)) DispatchQueue.main.async { [weak self] in self?.tableDirector.replace(withSections: [section]) diff --git a/ExpandableCellDemo-ios/Controllers/ManualLayoutViewController.swift b/ExpandableCellDemo-ios/Controllers/ManualLayoutViewController.swift index 59ec05b..423ae65 100644 --- a/ExpandableCellDemo-ios/Controllers/ManualLayoutViewController.swift +++ b/ExpandableCellDemo-ios/Controllers/ManualLayoutViewController.swift @@ -6,10 +6,10 @@ class ManualLayoutViewController: BaseViewController { return "Manual Layout" } - override var rows: [Row] { + override func rows(width: CGFloat) -> [Row] { return Array(0...100) .map { - TableRow(item: BaseExpandableCellViewModel(index: $0)) + TableRow(item: BaseExpandableCellViewModel(index: $0, width: width)) } } diff --git a/ExpandableCellDemo-ios/Controllers/PinLayoutViewController.swift b/ExpandableCellDemo-ios/Controllers/PinLayoutViewController.swift index 9a51f2f..92c62bf 100644 --- a/ExpandableCellDemo-ios/Controllers/PinLayoutViewController.swift +++ b/ExpandableCellDemo-ios/Controllers/PinLayoutViewController.swift @@ -6,10 +6,10 @@ class PinLayoutViewController: BaseViewController { return "Pin Layout" } - override var rows: [Row] { + override func rows(width: CGFloat) -> [Row] { return Array(0...100) .map { - TableRow(item: BaseExpandableCellViewModel(index: $0)) + TableRow(item: BaseExpandableCellViewModel(index: $0, width: width)) } } diff --git a/Podfile b/Podfile index 4acbe64..0489652 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ inhibit_all_warnings! target 'ExpandableCellDemo-ios' do - pod 'TableKit', :git => 'https://github.com/TouchInstinct/TableKit', :commit => '6ef5ad504eb3305db32ba98377bd896474914943' + pod 'TableKit', :git => 'https://github.com/TouchInstinct/TableKit', :commit => 'ff18a4d8b84e5970963e69968bc17399c8c855c4' pod 'PinLayout' pod 'SnapKit' diff --git a/Podfile.lock b/Podfile.lock index a0e17b8..6c09cc4 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,12 +1,12 @@ PODS: - PinLayout (1.8.6) - SnapKit (4.2.0) - - TableKit (2.8.0) + - TableKit (2.8.1) DEPENDENCIES: - PinLayout - SnapKit - - TableKit (from `https://github.com/TouchInstinct/TableKit`, commit `6ef5ad504eb3305db32ba98377bd896474914943`) + - TableKit (from `https://github.com/TouchInstinct/TableKit`, commit `ff18a4d8b84e5970963e69968bc17399c8c855c4`) SPEC REPOS: https://github.com/cocoapods/specs.git: @@ -15,19 +15,19 @@ SPEC REPOS: EXTERNAL SOURCES: TableKit: - :commit: 6ef5ad504eb3305db32ba98377bd896474914943 + :commit: ff18a4d8b84e5970963e69968bc17399c8c855c4 :git: https://github.com/TouchInstinct/TableKit CHECKOUT OPTIONS: TableKit: - :commit: 6ef5ad504eb3305db32ba98377bd896474914943 + :commit: ff18a4d8b84e5970963e69968bc17399c8c855c4 :git: https://github.com/TouchInstinct/TableKit SPEC CHECKSUMS: PinLayout: fe2a2432d6982588e208572005c941aeeae417ab SnapKit: fe8a619752f3f27075cc9a90244d75c6c3f27e2a - TableKit: d635663343d00e209f258e35d4ee0072ad1beb1a + TableKit: 18a0049dea981c1106409bdeebc763ef74d36f02 -PODFILE CHECKSUM: 6b497b1c0f4030ade5783db0b837662f14759131 +PODFILE CHECKSUM: edc056e576a26e481d1110118f841d78d997b954 COCOAPODS: 1.6.0.beta.2