Add Expandable States
This commit is contained in:
parent
0aa988d60d
commit
83de05fde7
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<ExpandableAutolayoutCell>(item: BaseExpandableCellViewModel(index: $0))
|
||||
TableRow<ExpandableAutolayoutCell>(item: BaseExpandableCellViewModel(index: $0, width: width))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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<ExpandableManualLayoutCell>(item: BaseExpandableCellViewModel(index: $0))
|
||||
TableRow<ExpandableManualLayoutCell>(item: BaseExpandableCellViewModel(index: $0, width: width))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ExpandablePinLayoutCell>(item: BaseExpandableCellViewModel(index: $0))
|
||||
TableRow<ExpandablePinLayoutCell>(item: BaseExpandableCellViewModel(index: $0, width: width))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
2
Podfile
2
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'
|
||||
|
|
|
|||
12
Podfile.lock
12
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue