diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..8618c4b --- /dev/null +++ b/.swiftformat @@ -0,0 +1,36 @@ +# format options +--binarygrouping none +--closingparen balanced +--commas inline +--conflictmarkers reject + +--decimalgrouping none +--octalgrouping none +--hexgrouping none + +--elseposition next-line +--guardelse same-line + +--empty void +--exponentcase lowercase +--exponentgrouping disabled +--fractiongrouping disabled +--fragment false +--header ignore +--hexliteralcase uppercase +--ifdef indent +--importgrouping alphabetized +--indent 4 +--linebreaks lf + +--patternlet hoist +--nospaceoperators +--self remove +--selfrequired +--stripunusedargs closure-only +--trailingclosures +--trimwhitespace always +--wraparguments before-first +--wrapcollections before-first +--xcodeindentation enabled +--disable redundantReturn, wrapMultilineStatementBraces \ No newline at end of file diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 48d7995..73e9427 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,6 +1,6 @@ PODS: - SwiftFormat/CLI (0.47.2) - - TableViewContent (3.0.2) + - TableViewContent (5.0.0) DEPENDENCIES: - SwiftFormat/CLI @@ -16,7 +16,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: SwiftFormat: 0315a7115b15fd4ea2d043d5f5c22e3c98f84078 - TableViewContent: c14053186a1579cb9dbfe1173383e1ae75caf48b + TableViewContent: de4ea2c1a0c0526a01cd18f8de0e5891e07ada69 PODFILE CHECKSUM: 35a8be3893318873897612545b5fea602dc5ba4d diff --git a/Example/TableViewContent.xcodeproj/project.pbxproj b/Example/TableViewContent.xcodeproj/project.pbxproj index 9a86362..23910b2 100644 --- a/Example/TableViewContent.xcodeproj/project.pbxproj +++ b/Example/TableViewContent.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ E90279612553DB300076246D /* CustomHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E90279602553DB300076246D /* CustomHeaderView.xib */; }; E94A52C3216BBD440074BF82 /* CustomRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94A52C1216BBD440074BF82 /* CustomRow.swift */; }; E94A52C4216BBD440074BF82 /* CustomTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E94A52C2216BBD440074BF82 /* CustomTableViewCell.xib */; }; + E9EECF1D256DA9E200606D3E /* ColorHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EECF1C256DA9E200606D3E /* ColorHeaderView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -53,6 +54,7 @@ E90279602553DB300076246D /* CustomHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CustomHeaderView.xib; sourceTree = ""; }; E94A52C1216BBD440074BF82 /* CustomRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomRow.swift; sourceTree = ""; }; E94A52C2216BBD440074BF82 /* CustomTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CustomTableViewCell.xib; sourceTree = ""; }; + E9EECF1C256DA9E200606D3E /* ColorHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorHeaderView.swift; sourceTree = ""; }; F1D0D8B5537BDF9787B774A8 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; FC47E3A09CA4830F57C9306C /* TableViewContent.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = TableViewContent.podspec; path = ../TableViewContent.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; /* End PBXFileReference section */ @@ -114,6 +116,7 @@ children = ( 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 607FACD71AFB9204008FA782 /* ViewController.swift */, + E9EECF1C256DA9E200606D3E /* ColorHeaderView.swift */, E94A52C1216BBD440074BF82 /* CustomRow.swift */, E94A52C2216BBD440074BF82 /* CustomTableViewCell.xib */, 607FACD91AFB9204008FA782 /* Main.storyboard */, @@ -366,6 +369,7 @@ E94A52C3216BBD440074BF82 /* CustomRow.swift in Sources */, 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, + E9EECF1D256DA9E200606D3E /* ColorHeaderView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/TableViewContent/ColorHeaderView.swift b/Example/TableViewContent/ColorHeaderView.swift new file mode 100644 index 0000000..b2117a7 --- /dev/null +++ b/Example/TableViewContent/ColorHeaderView.swift @@ -0,0 +1,31 @@ +// +// ColorHeaderView.swift +// TableViewContent_Example +// +// Created by Akira Matsuda on 2020/11/25. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import TableViewContent +import UIKit + +class ColorHeaderView: UIView, SectionConfigurable { + init(height: CGFloat) { + super.init(frame: .zero) + NSLayoutConstraint.activate([ + heightAnchor.constraint(equalToConstant: height) + ]) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(_ data: Any) { + guard let color = data as? UIColor else { + return + } + backgroundColor = color + } +} diff --git a/Example/TableViewContent/CustomRow.swift b/Example/TableViewContent/CustomRow.swift index 3786c29..5f5870a 100644 --- a/Example/TableViewContent/CustomRow.swift +++ b/Example/TableViewContent/CustomRow.swift @@ -10,19 +10,37 @@ import TableViewContent import UIKit class CustomTableViewCell: UITableViewCell { - @IBOutlet var button: UIButton! + public typealias Action = () -> Void + + @IBOutlet private var button: UIButton! + var buttonPressedAction: Action = {} + + override func awakeFromNib() { + super.awakeFromNib() + button.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside) + } + + @objc + private func buttonPressed(_: UIButton) { + buttonPressedAction() + } } -class CustomRow: RowRepresentation { +class CustomRow: Row { public typealias Action = () -> Void private var buttonPressedAction: Action = {} init() { - super.init(nib: UINib(nibName: "CustomTableViewCell", bundle: nil), cellType: CustomTableViewCell.self, reuseIdentifier: "CustomTableViewCell", data: nil) - configure(CustomTableViewCell.self) { [unowned self] cell, _, _ in - cell.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) - } + super.init( + .nib(.init(nibName: "CustomTableViewCell", bundle: nil)), + reuseIdentifier: NSStringFromClass(CustomTableViewCell.self) + ) + selectionStyle(.none) + } + + override func defaultCellConfiguration(_ cell: CustomTableViewCell, _ indexPath: IndexPath) { + cell.buttonPressedAction = buttonPressedAction } convenience init(_ action: @escaping Action) { @@ -36,7 +54,8 @@ class CustomRow: RowRepresentation { return self } - @objc private func buttonPressed() { + @objc + private func buttonPressed() { buttonPressedAction() } } diff --git a/Example/TableViewContent/CustomTableViewCell.xib b/Example/TableViewContent/CustomTableViewCell.xib index 17ea601..3e6bb63 100644 --- a/Example/TableViewContent/CustomTableViewCell.xib +++ b/Example/TableViewContent/CustomTableViewCell.xib @@ -1,9 +1,9 @@ - + - + @@ -18,15 +18,15 @@ - - + + diff --git a/Example/TableViewContent/ViewController.swift b/Example/TableViewContent/ViewController.swift index 4a4cfe2..0fe5e79 100644 --- a/Example/TableViewContent/ViewController.swift +++ b/Example/TableViewContent/ViewController.swift @@ -11,66 +11,194 @@ import UIKit class ViewController: UIViewController { @IBOutlet var tableView: UITableView! - var delegate: ContentDelegate? + var delegate: Delegate? + + private var textField: UITextField? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. title = "Example" - let dataSource = ContentDataSource { - Section { - DefaultRow(title: "title") - DefaultRow(title: "title", style: .subtitle) - .detailText("subtitle") - DefaultRow(title: "title", style: .value1) - .detailText("value1") - DefaultRow(title: "title", style: .value2) - .accessoryType(.disclosureIndicator) - .detailText("value2") - .didSelect { _, _, _ in - let viewController = UIStoryboard( - name: "Main", - bundle: nil - ).instantiateViewController(withIdentifier: "ViewController") - self.navigationController?.pushViewController(viewController, animated: true) + let dataSource = DataSource { + basicSection() + configureSection() + switchSection() + customCellSection() + viewHeaderSection() + nibHeaderSection() + multipleSelectionSection() + exclusiveSelectionSection() + } + appendSection(dataSource) + dataSource.presentSectinIndex = true + delegate = Delegate(dataSource: dataSource, tableView: tableView) + delegate?.clearSelectionAutomatically = true + tableView.delegate = delegate + tableView.dataSource = dataSource + } + + func basicSection() -> Section { + return Section { + DefaultRow(title: "title") + DefaultRow(title: "title", style: .subtitle) + .detailText("subtitle") + DefaultRow(title: "title", style: .value1) + .detailText("value1") + .updateAfterSelected(true) + .didSelect { _, _, row in + row.configuration.title = "updated" + } + DefaultRow(title: "title", style: .value2) + .accessoryType(.disclosureIndicator) + .detailText("value2") + .didSelect { _, _, _ in + let viewController = UIStoryboard( + name: "Main", + bundle: nil + ).instantiateViewController(withIdentifier: "ViewController") + self.navigationController?.pushViewController(viewController, animated: true) + } + DefaultRow(title: "Swipe") + .trailingSwipeActions { () -> UISwipeActionsConfiguration? in + let action = UIContextualAction( + style: .destructive, + title: "trailing" + ) { _, _, handler in + print("trailing action") + handler(true) } - } - Section { - SwitchRow(title: "Switch") - .toggled { isOn in - print("Switch1 \(isOn)") + let configuration = UISwipeActionsConfiguration(actions: [action]) + configuration.performsFirstActionWithFullSwipe = true + return configuration + } + .leadingSwipeActions { () -> UISwipeActionsConfiguration? in + let action = UIContextualAction( + style: .destructive, + title: "leading" + ) { _, _, handler in + print("leading action") + handler(true) } - SwitchRow(title: "Switch2", isOn: true) - .toggled { isOn in - print("Switch2 \(isOn)") - }.didSelect { _, _, isOn in - print("\(String(describing: isOn))") + action.backgroundColor = .blue + let configuration = UISwipeActionsConfiguration(actions: [action]) + configuration.performsFirstActionWithFullSwipe = true + return configuration + } + } + .sectionIndexTitle("Section") + } + + func configureSection() -> Section { + return Section { + DefaultRow(title: "configure", reuseIdentifier: "configure cell") + .bind { [unowned self] row in + if let textField = self.textField { + row.configuration.title = textField.text } - } - Section { - CustomRow { - print("button pressed") } - } - Section() - .header(.title("header")) - .contents { section in - for i in 0 ... 5 { - section.append(DefaultRow(title: "\(i)")) + .configure { cell, _, _ in + cell.backgroundColor = .red + } + .didSelect { [unowned self] tableView, indexPath, _ in + let alert = UIAlertController(title: "Please input text", message: "", preferredStyle: .alert) + let action = UIAlertAction(title: "OK", style: .default) { _ in + tableView.reloadRows(at: [indexPath], with: .automatic) + } + alert.addTextField { [unowned self] textField in + self.textField = textField } + alert.addAction(action) + self.present(alert, animated: true, completion: nil) + } + } + } + + func switchSection() -> Section { + return Section { + SwitchRow(title: "Switch") + .toggled { isOn in + print("Switch1 \(isOn)") + } + SwitchRow(title: "Switch2", isOn: true) + .toggled { isOn in + print("Switch2 \(isOn)") } - .footer(.title("footer")) - Section() - .header(.nib("custom header", UINib(nibName: "CustomHeaderView", bundle: nil))) - .contents { section in - for i in 0 ... 5 { - section.append(DefaultRow(title: "\(i)")) + .didSelect { _, _, row in + if let row = row as? SwitchRow { + row.isOn.toggle() } } - .footer(.nib("custom footer", UINib(nibName: "CustomHeaderView", bundle: nil))) + .updateAfterSelected(true) + } + .sectionIndexTitle("Switch Section") + } + + func customCellSection() -> Section { + return Section { + CustomRow { + print("button pressed") + } + } + .sectionIndexTitle("Button Section") + } + + func viewHeaderSection() -> Section { + return Section() + .header(.view(ColorHeaderView(height: 10), UIColor.green)) + .rows { + DefaultRow(title: "a") + DefaultRow(title: "b") + DefaultRow(title: "c") + } + .footer(.view(ColorHeaderView(height: 10), UIColor.blue)) + .sectionIndexTitle("Header Section2") + } + + func nibHeaderSection() -> Section { + return Section() + .header(.nib(UINib(nibName: "CustomHeaderView", bundle: nil), "custom header")) + .rows { + DefaultRow(title: "a") + DefaultRow(title: "b") + DefaultRow(title: "c") + } + .footer(.nib(UINib(nibName: "CustomHeaderView", bundle: nil), "custom footer")) + .sectionIndexTitle("Custom Header Section") + } + + func multipleSelectionSection() -> Section { + return SelectionSection() + .header(.title("Multiple Selection")) + .rows { + CheckmarkRow(title: "1") + CheckmarkRow(title: "2") + CheckmarkRow(title: "3") + } + } + + func exclusiveSelectionSection() -> Section { + return SelectionSection(exclusive: true, destructive: true) + .header(.title("Exclusive")) + .rows { + CheckmarkRow(title: "1") + CheckmarkRow(title: "2") + CheckmarkRow(title: "3") + } + } + + func appendSection(_ dataSource: DataSource) { + dataSource.sections { dataSource in + dataSource.append( + Section() + .header(.title("header")) + .rows { section in + for i in 0 ... 5 { + section.append(DefaultRow(title: "\(i)")) + } + } + .footer(.title("footer")) + .sectionIndexTitle("Header Section") + ) } - delegate = ContentDelegate(dataSource: dataSource) - tableView.delegate = delegate - tableView.dataSource = dataSource } } diff --git a/Package.swift b/Package.swift index c1b3457..c9d7c47 100644 --- a/Package.swift +++ b/Package.swift @@ -5,16 +5,16 @@ import PackageDescription let package = Package( name: "TableViewContent", platforms: [ - .iOS(.v11), + .iOS(.v11) ], products: [ - .library(name: "TableViewContent", targets: ["TableViewContent"]), + .library(name: "TableViewContent", targets: ["TableViewContent"]) ], targets: [ .target(name: "TableViewContent", path: "TableViewContent/Classes"), - .testTarget(name: "TableViewContentTest", dependencies: ["TableViewContent"]), + .testTarget(name: "TableViewContentTest", dependencies: ["TableViewContent"]) ], swiftLanguageVersions: [ - .v5, + .v5 ] ) diff --git a/README.md b/README.md index a0af919..b2cca38 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ pod 'TableViewContent' You can declare table view sections and cells as follows: -``` +```swift Section { DefaultRow(title: "title") DefaultRow(title: "title", style: .subtitle) @@ -40,7 +40,7 @@ Section { To handle cell selection, call `didSelect` method. -``` +```swift DefaultRow(title: "title", style: .value2) .accessoryType(.disclosureIndicator) .detailText("value2") @@ -50,18 +50,40 @@ DefaultRow(title: "title", style: .value2) } ``` -Define class that inherit `RowRepresentation` for implementing custom row. -``` -class CustomRow: RowRepresentation { +Define class that inherit `Row` for implementing custom row. +```swift +class CustomTableViewCell: UITableViewCell { + public typealias Action = () -> Void + + @IBOutlet private var button: UIButton! + var buttonPressedAction: Action = {} + + override func awakeFromNib() { + super.awakeFromNib() + button.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside) + } + + @objc + private func buttonPressed(_: UIButton) { + buttonPressedAction() + } +} + +class CustomRow: Row { public typealias Action = () -> Void private var buttonPressedAction: Action = {} init() { - super.init(nib: UINib(nibName: "CustomTableViewCell", bundle: nil), cellType: CustomTableViewCell.self, reuseIdentifier: "CustomTableViewCell", data: nil) - configure(CustomTableViewCell.self) { [unowned self] cell, _, _ in - cell.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) - } + super.init( + .nib(.init(nibName: "CustomTableViewCell", bundle: nil)), + reuseIdentifier: NSStringFromClass(CustomTableViewCell.self) + ) + selectionStyle(.none) + } + + override func defaultCellConfiguration(_ cell: CustomTableViewCell, _ indexPath: IndexPath) { + cell.buttonPressedAction = buttonPressedAction } convenience init(_ action: @escaping Action) { @@ -75,14 +97,11 @@ class CustomRow: RowRepresentation { return self } - @objc private func buttonPressed() { + @objc + private func buttonPressed() { buttonPressedAction() } } - -class CustomTableViewCell: UITableViewCell { - @IBOutlet var button: UIButton! -} ``` See example code to lean advanced usage. diff --git a/TableViewContent.podspec b/TableViewContent.podspec index 908ce16..d23bfc1 100644 --- a/TableViewContent.podspec +++ b/TableViewContent.podspec @@ -8,8 +8,8 @@ Pod::Spec.new do |s| s.name = 'TableViewContent' - s.version = '4.0.0' - s.summary = 'Declare tableView contents, inspired DataSourceKit.' + s.version = '5.0.0' + s.summary = 'Declarative table view configure library.' # This description is used to generate tags and improve search results. # * Think: What does it do? Why did you write it? What is the focus? @@ -18,7 +18,7 @@ Pod::Spec.new do |s| # * Finally, don't worry about the indent, CocoaPods strips it! s.description = <<-DESC -Declare tableView row and section header/footer, inspired DataSourceKit. +Declare tableView row and section header/footer easy way. DESC s.homepage = 'https://github.com/0x0c/TableViewContent' diff --git a/TableViewContent/Classes/ContentDataSource.swift b/TableViewContent/Classes/ContentDataSource.swift deleted file mode 100644 index 39805b0..0000000 --- a/TableViewContent/Classes/ContentDataSource.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// ContentDataSource.swift -// Pods-TableViewContent_Example -// -// Created by Akira Matsuda on 2019/06/19. -// - -import UIKit - -@_functionBuilder -public struct SectionBuilder { - public static func buildBlock(_ items: Section...) -> [Section] { - items - } -} - -open class ContentDataSource: NSObject, UITableViewDataSource { - internal var sections: [Section] = [] - open var registeredReuseIdentifiers = [] as [String] - - public init(_ sections: [Section]) { - self.sections = sections - } - - public init(@SectionBuilder _ sections: () -> [Section]) { - self.sections = sections() - } - - open func numberOfSections(in _: UITableView) -> Int { - sections.count - } - - open func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { - let s = sections[section] - return s.contents.count - } - - open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let section = sections[indexPath.section] - let row = section.contents[indexPath.row] - - switch row.source { - case let .nib(nib): - if !registeredReuseIdentifiers.contains(row.reuseIdentifier) { - registeredReuseIdentifiers.append(row.reuseIdentifier) - tableView.register(nib, forCellReuseIdentifier: row.reuseIdentifier) - } - let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier, for: indexPath) - row._configure(cell, indexPath) - return cell - - case let .class(cellClass): - if !registeredReuseIdentifiers.contains(row.reuseIdentifier) { - registeredReuseIdentifiers.append(row.reuseIdentifier) - tableView.register(cellClass, forCellReuseIdentifier: row.reuseIdentifier) - } - let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier, for: indexPath) - row._configure(cell, indexPath) - return cell - - case let .style(cellStyle): - if let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier) { - row._configure(cell, indexPath) - return cell - } else { - let cell = UITableViewCell(style: cellStyle, reuseIdentifier: row.reuseIdentifier) - row._configure(cell, indexPath) - return cell - } - } - } - - open func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { - let s = sections[section] - switch s.headerView { - case let .title(text): - return text - default: - return nil - } - } - - open func tableView(_: UITableView, titleForFooterInSection section: Int) -> String? { - let s = sections[section] - switch s.footerView { - case let .title(text): - return text - default: - return nil - } - } -} diff --git a/TableViewContent/Classes/ContentDelegate.swift b/TableViewContent/Classes/ContentDelegate.swift deleted file mode 100644 index 7fba957..0000000 --- a/TableViewContent/Classes/ContentDelegate.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// ContentDelegate.swift -// Pods-TableViewContent_Example -// -// Created by Akira Matsuda on 2019/06/19. -// - -import UIKit - -open class ContentDelegate: NSObject, UITableViewDelegate { - private let dataSource: ContentDataSource - - public init(dataSource: ContentDataSource) { - self.dataSource = dataSource - } - - open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let section = dataSource.sections[indexPath.section] - let row = section.contents[indexPath.row] - if let action = row.action { - action(tableView, indexPath, row.data) - } else if let action = section.selectedAction { - action(tableView, indexPath, row.data) - } - tableView.deselectRow(at: indexPath, animated: true) - } - - public func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let s = dataSource.sections[section] - guard let headerView = s.headerView else { - return nil - } - return headerView.sectionView - } - - public func tableView(_: UITableView, viewForFooterInSection section: Int) -> UIView? { - let s = dataSource.sections[section] - guard let footerView = s.footerView else { - return nil - } - return footerView.sectionView - } - - public func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - let s = dataSource.sections[section] - if s.headerView == nil { - return 0 - } - return UITableView.automaticDimension - } - - public func tableView(_: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { - let s = dataSource.sections[section] - if s.headerView == nil { - return 0 - } - return 1 - } - - public func tableView(_: UITableView, heightForFooterInSection section: Int) -> CGFloat { - let s = dataSource.sections[section] - if s.footerView == nil { - return 0 - } - return UITableView.automaticDimension - } - - public func tableView(_: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat { - let s = dataSource.sections[section] - if s.footerView == nil { - return 0 - } - return 1 - } -} diff --git a/TableViewContent/Classes/DataSource.swift b/TableViewContent/Classes/DataSource.swift new file mode 100644 index 0000000..a89d1d8 --- /dev/null +++ b/TableViewContent/Classes/DataSource.swift @@ -0,0 +1,118 @@ +// +// ContentDataSource.swift +// Pods-TableViewContent_Example +// +// Created by Akira Matsuda on 2019/06/19. +// + +import UIKit + +@_functionBuilder +public enum SectionBuilder { + public static func buildBlock(_ items: Section...) -> [Section] { + items + } +} + +open class DataSource: NSObject, UITableViewDataSource { + public private(set) var sections: [Section] = [] + public private(set) var registeredReuseIdentifiers = [] as [String] + open var presentSectinIndex: Bool = false + + public init(_ sections: [Section]) { + self.sections = sections + } + + public init(@SectionBuilder _ sections: () -> [Section]) { + self.sections = sections() + } + + @discardableResult + public func sections(_ closure: (DataSource) -> Void) -> Self { + closure(self) + return self + } + + @discardableResult + public func append(_ section: Section) -> Self { + sections.append(section) + return self + } + + public func numberOfSections(in _: UITableView) -> Int { + sections.count + } + + public func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { + let s = sections[section] + return s.rows.count + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let section = sections[indexPath.section] + let row = section.rows[indexPath.row] + + switch row.representation { + case let .nib(nibClass): + if !registeredReuseIdentifiers.contains(row.reuseIdentifier) { + registeredReuseIdentifiers.append(row.reuseIdentifier) + tableView.register(nibClass, forCellReuseIdentifier: row.reuseIdentifier) + } + case let .class(cellClass): + if !registeredReuseIdentifiers.contains(row.reuseIdentifier) { + registeredReuseIdentifiers.append(row.reuseIdentifier) + tableView.register(cellClass, forCellReuseIdentifier: row.reuseIdentifier) + } + case let .cellStyle(style): + if let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier) { + row.prepare(cell, indexPath: indexPath) + return cell + } + else { + let cell = UITableViewCell(style: style, reuseIdentifier: row.reuseIdentifier) + row.prepare(cell, indexPath: indexPath) + return cell + } + } + + let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier, for: indexPath) + row.prepare(cell, indexPath: indexPath) + return cell + } + + public func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { + let s = sections[section] + switch s.headerView { + case let .title(text): + return text + default: + return nil + } + } + + public func tableView(_: UITableView, titleForFooterInSection section: Int) -> String? { + let s = sections[section] + switch s.footerView { + case let .title(text): + return text + default: + return nil + } + } + + public func sectionIndexTitles(for tableView: UITableView) -> [String]? { + if presentSectinIndex { + var indexTitles = [String]() + for section in sections { + if let title = section.sectionIndexTitle { + indexTitles.append(title) + } + else { + return nil + } + } + return indexTitles + } + return nil + } +} diff --git a/TableViewContent/Classes/Delegate.swift b/TableViewContent/Classes/Delegate.swift new file mode 100644 index 0000000..ac8ac78 --- /dev/null +++ b/TableViewContent/Classes/Delegate.swift @@ -0,0 +1,104 @@ +// +// ContentDelegate.swift +// Pods-TableViewContent_Example +// +// Created by Akira Matsuda on 2019/06/19. +// + +import UIKit + +open class Delegate: NSObject, UITableViewDelegate { + private var dataSource: DataSource + private var tableView: UITableView + open var clearSelectionAutomatically: Bool = false + + public init(dataSource: DataSource, tableView: UITableView) { + self.dataSource = dataSource + self.tableView = tableView + } + + public func reload(_ dataSource: DataSource) { + self.dataSource = dataSource + tableView.reloadData() + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if clearSelectionAutomatically { + tableView.deselectRow(at: indexPath, animated: true) + } + let section = dataSource.sections[indexPath.section] + let row = section.rows[indexPath.row] + if let action = row.selectedAction { + action(tableView, indexPath) + } + else if let action = section.selectedAction { + action(tableView, indexPath) + } + if section.updateAfterSelected { + tableView.reloadSections(IndexSet(integer: indexPath.section), with: section.updateAnimation) + } + else if row.updateAfterSelected { + tableView.reloadRows(at: [indexPath], with: row.updateAnimation) + } + } + + public func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let s = dataSource.sections[section] + guard let headerView = s.headerView else { + return nil + } + return headerView.sectionView + } + + public func tableView(_: UITableView, viewForFooterInSection section: Int) -> UIView? { + let s = dataSource.sections[section] + guard let footerView = s.footerView else { + return nil + } + return footerView.sectionView + } + + public func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + let s = dataSource.sections[section] + if s.headerView == nil { + return 0 + } + return UITableView.automaticDimension + } + + public func tableView(_: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { + let s = dataSource.sections[section] + if s.headerView == nil { + return 0 + } + return 1 + } + + public func tableView(_: UITableView, heightForFooterInSection section: Int) -> CGFloat { + let s = dataSource.sections[section] + if s.footerView == nil { + return 0 + } + return UITableView.automaticDimension + } + + public func tableView(_: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat { + let s = dataSource.sections[section] + if s.footerView == nil { + return 0 + } + return 1 + } + + public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let section = dataSource.sections[indexPath.section] + let row = section.rows[indexPath.row] + return row.trailingSwipeActionsConfiguration?() + } + + public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let section = dataSource.sections[indexPath.section] + let row = section.rows[indexPath.row] + return row.leadingSwipeActionsConfiguration?() + } +} diff --git a/TableViewContent/Classes/Model/CellConfiguration.swift b/TableViewContent/Classes/Model/CellConfiguration.swift new file mode 100644 index 0000000..266dfda --- /dev/null +++ b/TableViewContent/Classes/Model/CellConfiguration.swift @@ -0,0 +1,20 @@ +// +// CellConfiguration.swift +// TableViewContent +// +// Created by Akira Matsuda on 2020/11/25. +// + +import UIKit + +open class CellConfiguration { + public var title: String? + public var detailText: String? + public var image: UIImage? + public var selectionStyle: UITableViewCell.SelectionStyle = .blue + public var accessoryType: UITableViewCell.AccessoryType = .none + public var accessoryView: UIView? + public var editingAccessoryType: UITableViewCell.AccessoryType = .none + public var editingAccessoryView: UIView? + public var style: UITableViewCell.CellStyle = .default +} diff --git a/TableViewContent/Classes/Model/CellRepresentation.swift b/TableViewContent/Classes/Model/CellRepresentation.swift new file mode 100644 index 0000000..dc53c60 --- /dev/null +++ b/TableViewContent/Classes/Model/CellRepresentation.swift @@ -0,0 +1,14 @@ +// +// CellRepresentation.swift +// TableViewContent +// +// Created by Akira Matsuda on 2020/11/25. +// + +import UIKit + +public enum CellRepresentation { + case nib(UINib) + case `class`(AnyClass) + case cellStyle(UITableViewCell.CellStyle) +} diff --git a/TableViewContent/Classes/Protocol/RowRepresentation.swift b/TableViewContent/Classes/Protocol/RowRepresentation.swift new file mode 100644 index 0000000..c12e5dd --- /dev/null +++ b/TableViewContent/Classes/Protocol/RowRepresentation.swift @@ -0,0 +1,20 @@ +// +// RowRepresentation.swift +// TableViewContent +// +// Created by Akira Matsuda on 2020/11/25. +// + +import UIKit + +public protocol RowRepresentation { + var updateAfterSelected: Bool { get set } + var updateAnimation: UITableView.RowAnimation { get set } + var configuration: CellConfiguration { get set } + var reuseIdentifier: String { get } + var representation: CellRepresentation { get } + var selectedAction: ((UITableView, IndexPath) -> Void)? { get } + var trailingSwipeActionsConfiguration: (() -> UISwipeActionsConfiguration?)? { get set } + var leadingSwipeActionsConfiguration: (() -> UISwipeActionsConfiguration?)? { get set } + func prepare(_ cell: UITableViewCell, indexPath: IndexPath) +} diff --git a/TableViewContent/Classes/Protocol/SectionConfigurable.swift b/TableViewContent/Classes/Protocol/SectionConfigurable.swift new file mode 100644 index 0000000..fb29630 --- /dev/null +++ b/TableViewContent/Classes/Protocol/SectionConfigurable.swift @@ -0,0 +1,12 @@ +// +// SectionConfigurable.swift +// TableViewContent +// +// Created by Akira Matsuda on 2020/11/25. +// + +import Foundation + +public protocol SectionConfigurable { + func configure(_ data: Any) +} diff --git a/TableViewContent/Classes/Row.swift b/TableViewContent/Classes/Row.swift deleted file mode 100644 index 2d642be..0000000 --- a/TableViewContent/Classes/Row.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// TableCellContent.swift -// Pods-TableViewContent_Example -// -// Created by Akira Matsuda on 2018/10/12. -// - -import UIKit - -open class Row: RowRepresentation { - public init(title: String, cellType _: Cell.Type, reuseIdentifier: String, style: UITableViewCell.CellStyle) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - self.title = title - self.style = style - configure(UITableViewCell.self) { [unowned self] cell, _, _ in - cell.textLabel?.text = self.title - cell.detailTextLabel?.text = self.detailText - cell.imageView?.image = self.image - - cell.selectionStyle = self.selectionStyle - - cell.accessoryView = self.accessoryView - cell.accessoryType = self.accessoryType - - cell.editingAccessoryView = self.editingAccessoryView - cell.editingAccessoryType = self.editingAccessoryType - } - } - - @discardableResult - open func configure(_ configuration: (Row) -> Void) -> Self { - configuration(self) - return self - } -} - -open class DefaultRow: Row { - public init(title: String, style: UITableViewCell.CellStyle = .default) { - super.init(title: title, cellType: UITableViewCell.self, reuseIdentifier: "\(NSStringFromClass(DefaultRow.self))-\(style.rawValue)", style: style) - } -} diff --git a/TableViewContent/Classes/Row/CheckmarkRow.swift b/TableViewContent/Classes/Row/CheckmarkRow.swift new file mode 100644 index 0000000..c00d39a --- /dev/null +++ b/TableViewContent/Classes/Row/CheckmarkRow.swift @@ -0,0 +1,21 @@ +// +// CheckmarkRow.swift +// TableViewContent +// +// Created by Akira Matsuda on 2020/11/25. +// + +import UIKit + +open class CheckmarkRow: DefaultRow { + public var checked: Bool = false + + override open func defaultCellConfiguration(_ cell: UITableViewCell, _ indexPath: IndexPath) { + if checked { + cell.accessoryType = .checkmark + } + else { + cell.accessoryType = .none + } + } +} diff --git a/TableViewContent/Classes/Row/DefaultRow.swift b/TableViewContent/Classes/Row/DefaultRow.swift new file mode 100644 index 0000000..058cc14 --- /dev/null +++ b/TableViewContent/Classes/Row/DefaultRow.swift @@ -0,0 +1,21 @@ +// +// TableCellContent.swift +// Pods-TableViewContent_Example +// +// Created by Akira Matsuda on 2018/10/12. +// + +import UIKit + +open class DefaultRow: Row { + public init(title: String, style: UITableViewCell.CellStyle = .default, reuseIdentifier: String? = nil) { + var identifier: String { + if let identifier = reuseIdentifier { + return identifier + } + return "\(NSStringFromClass(Self.self))-\(style.rawValue)" + } + super.init(.cellStyle(style), reuseIdentifier: identifier) + super.title(title) + } +} diff --git a/TableViewContent/Classes/Row/Row.swift b/TableViewContent/Classes/Row/Row.swift new file mode 100644 index 0000000..7e7d9ba --- /dev/null +++ b/TableViewContent/Classes/Row/Row.swift @@ -0,0 +1,138 @@ +// +// TableViewContent.swift +// Pods-TableViewContent_Example +// +// Created by Akira Matsuda on 2018/10/08. +// + +import UIKit + +open class Row: RowRepresentation { + private var configureCell: ((Cell, IndexPath, Row) -> Void)? + public var bind: ((Row) -> Void)? + public var updateAfterSelected: Bool = false + public var updateAnimation: UITableView.RowAnimation = .automatic + public var configuration = CellConfiguration() + public let reuseIdentifier: String + public let representation: CellRepresentation + public var selectedAction: ((UITableView, IndexPath) -> Void)? + public var trailingSwipeActionsConfiguration: (() -> UISwipeActionsConfiguration?)? + public var leadingSwipeActionsConfiguration: (() -> UISwipeActionsConfiguration?)? + + public init( + _ representation: CellRepresentation, + reuseIdentifier: String + ) { + self.representation = representation + self.reuseIdentifier = reuseIdentifier + } + + open func prepare(_ cell: UITableViewCell, indexPath: IndexPath) { + bind?(self) + cell.textLabel?.text = configuration.title + cell.detailTextLabel?.text = configuration.detailText + cell.imageView?.image = configuration.image + cell.selectionStyle = configuration.selectionStyle + cell.accessoryView = configuration.accessoryView + cell.accessoryType = configuration.accessoryType + cell.editingAccessoryView = configuration.editingAccessoryView + cell.editingAccessoryType = configuration.editingAccessoryType + defaultCellConfiguration(cell as! Cell, indexPath) + configureCell?(cell as! Cell, indexPath, self) + } + + open func defaultCellConfiguration(_ cell: Cell, _ indexPath: IndexPath) {} + + @discardableResult + open func bind(_ bind: @escaping (Row) -> Void) -> Self { + self.bind = bind + return self + } + + @discardableResult + open func configuration(_ configuration: CellConfiguration) -> Self { + self.configuration = configuration + return self + } + + @discardableResult + open func updateAfterSelected(_ update: Bool, animation: UITableView.RowAnimation = .automatic) -> Self { + updateAfterSelected = update + updateAnimation = animation + return self + } + + @discardableResult + open func configure(_ configuration: ((Cell, IndexPath, Row) -> Void)?) -> Self { + configureCell = configuration + return self + } + + @discardableResult + open func didSelect(_ action: @escaping (UITableView, IndexPath, Row) -> Void) -> Self { + selectedAction = { tableView, indexPath in + action(tableView, indexPath, self) + } + return self + } + + @discardableResult + open func title(_ title: String?) -> Self { + configuration.title = title + return self + } + + @discardableResult + open func detailText(_ text: String?) -> Self { + configuration.detailText = text + return self + } + + @discardableResult + open func image(_ image: UIImage?) -> Self { + configuration.image = image + return self + } + + @discardableResult + open func selectionStyle(_ style: UITableViewCell.SelectionStyle) -> Self { + configuration.selectionStyle = style + return self + } + + @discardableResult + open func accessoryType(_ type: UITableViewCell.AccessoryType) -> Self { + configuration.accessoryType = type + return self + } + + @discardableResult + open func accessoryView(_ view: UIView?) -> Self { + configuration.accessoryView = view + return self + } + + @discardableResult + open func editingAccessoryType(_ type: UITableViewCell.AccessoryType) -> Self { + configuration.editingAccessoryType = type + return self + } + + @discardableResult + open func editingAccessoryView(_ view: UIView?) -> Self { + configuration.editingAccessoryView = view + return self + } + + @discardableResult + open func trailingSwipeActions(_ actions: @escaping () -> UISwipeActionsConfiguration?) -> Self { + trailingSwipeActionsConfiguration = actions + return self + } + + @discardableResult + open func leadingSwipeActions(_ actions: @escaping () -> UISwipeActionsConfiguration?) -> Self { + leadingSwipeActionsConfiguration = actions + return self + } +} diff --git a/TableViewContent/Classes/Row/SwitchRow.swift b/TableViewContent/Classes/Row/SwitchRow.swift new file mode 100644 index 0000000..34fa6a8 --- /dev/null +++ b/TableViewContent/Classes/Row/SwitchRow.swift @@ -0,0 +1,82 @@ +// +// SwitchRow.swift +// Pods +// +// Created by Akira Matsuda on 2020/11/05. +// + +import UIKit + +open class SwitchRow: Row { + private var toggledAction: ((Bool) -> Void)? + public var isOn: Bool { + didSet { + toggledAction?(isOn) + } + } + + public init(title: String, isOn: Bool = false) { + self.isOn = isOn + super.init( + .class(SwitchTableViewCell.self), + reuseIdentifier: NSStringFromClass(SwitchTableViewCell.self) + ) + self.title(title) + selectionStyle(.none) + } + + override open func defaultCellConfiguration(_ cell: SwitchTableViewCell, _ indexPath: IndexPath) { + cell.prepareAccessoryView() + cell.isOn = isOn + cell.toggled { [weak self] newValue in + guard let weakSelf = self else { + return + } + weakSelf.isOn = newValue + } + } + + @discardableResult + open func toggled(_ toggleAction: @escaping (Bool) -> Void) -> Self { + toggledAction = toggleAction + return self + } +} + +open class SwitchTableViewCell: UITableViewCell { + private let sw = UISwitch() + private var toggledAction: ((Bool) -> Void)? + + public var isOn: Bool { + get { + sw.isOn + } + set { + sw.setOn(newValue, animated: true) + } + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + accessoryView = sw + sw.addTarget(self, action: #selector(_toggled(_:)), for: .valueChanged) + } + + @available(*, unavailable) + public required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc + private func _toggled(_ sender: UISwitch) { + toggledAction?(sender.isOn) + } + + func toggled(action: ((Bool) -> Void)?) { + toggledAction = action + } + + func prepareAccessoryView() { + accessoryView = sw + } +} diff --git a/TableViewContent/Classes/RowRepresentation.swift b/TableViewContent/Classes/RowRepresentation.swift deleted file mode 100644 index d916e4b..0000000 --- a/TableViewContent/Classes/RowRepresentation.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// TableViewContent.swift -// Pods-TableViewContent_Example -// -// Created by Akira Matsuda on 2018/10/08. -// - -import UIKit - -public enum RepresentationSource { - case nib(UINib) - case `class`(AnyClass) - case style(UITableViewCell.CellStyle) -} - -open class RowRepresentation { - public let source: RepresentationSource - - public let reuseIdentifier: String - public var action: ((UITableView, IndexPath, Any?) -> Void)? - public var data: Any? - public var title: String? - public var detailText: String? - public var image: UIImage? - public var selectionStyle: UITableViewCell.SelectionStyle = .blue - public var accessoryType: UITableViewCell.AccessoryType = .none - public var accessoryView: UIView? - public var editingAccessoryType: UITableViewCell.AccessoryType = .none - public var editingAccessoryView: UIView? - public var style: UITableViewCell.CellStyle = .default - - internal var _configure: (Any, IndexPath) -> Void = { _, _ in } - - public init(style: UITableViewCell.CellStyle, reuseIdentifier: String, data: Any? = nil) { - source = .style(style) - self.reuseIdentifier = reuseIdentifier - self.data = data - } - - public init(_ cellType: Cell.Type, reuseIdentifier: String, data: Any? = nil, configuration: ((Cell, IndexPath, Any?) -> Void)? = nil) { - source = .class(cellType as! AnyClass) - self.reuseIdentifier = reuseIdentifier - self.data = data - configure(cellType, configuration: configuration) - } - - public init(nib: UINib, cellType: Cell.Type, reuseIdentifier: String, data: Any? = nil, configuration: ((Cell, IndexPath, Any?) -> Void)? = nil) { - source = .nib(nib) - self.reuseIdentifier = reuseIdentifier - self.data = data - configure(cellType, configuration: configuration) - } - - @discardableResult - open func didSelect(_ selectedAction: @escaping (UITableView, IndexPath, Any?) -> Void) -> Self { - action = selectedAction - return self - } - - @discardableResult - open func configure(_: Cell.Type, configuration: ((Cell, IndexPath, Any?) -> Void)?) -> Self { - _configure = { [unowned self] cell, indexPath in - guard let cell = cell as? Cell else { - fatalError("Could not cast cell to \(Cell.self)") - } - - if let configuration = configuration { - configuration(cell, indexPath, self.data) - } - } - - return self - } - - @discardableResult - open func title(_ title: String?) -> Self { - self.title = title - return self - } - - @discardableResult - open func detailText(_ text: String?) -> Self { - detailText = text - return self - } - - @discardableResult - open func image(_ image: UIImage?) -> Self { - self.image = image - return self - } - - @discardableResult - open func selectionStyle(_ style: UITableViewCell.SelectionStyle) -> Self { - selectionStyle = style - return self - } - - @discardableResult - open func accessoryType(_ type: UITableViewCell.AccessoryType) -> Self { - accessoryType = type - return self - } - - @discardableResult - open func accessoryView(_ view: UIView?) -> Self { - accessoryView = view - return self - } - - @discardableResult - open func editingAccessoryType(_ type: UITableViewCell.AccessoryType) -> Self { - editingAccessoryType = type - return self - } - - @discardableResult - open func editingAccessoryView(_ view: UIView?) -> Self { - editingAccessoryView = view - return self - } -} diff --git a/TableViewContent/Classes/Section.swift b/TableViewContent/Classes/Section.swift deleted file mode 100644 index 6ebbf8d..0000000 --- a/TableViewContent/Classes/Section.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// TableViewSection.swift -// Pods-TableViewContent_Example -// -// Created by Akira Matsuda on 2019/06/19. -// - -import UIKit - -@_functionBuilder -public struct CellBuilder { - public static func buildBlock(_ items: RowRepresentation...) -> [RowRepresentation] { - items - } -} - -public protocol SectionConfigurable { - func configure(_ data: Any) -} - -public typealias SectionViewRepresentation = UIView & SectionConfigurable - -public enum SectionSupplementalyView { - case title(String) - case nib(Any, UINib) - - var sectionView: UIView? { - switch self { - case .title: - return nil - case let .nib(data, nib): - let internalView = nib.instantiate(withOwner: nil, options: nil).first as? SectionViewRepresentation - internalView?.configure(data) - return internalView - } - } -} - -open class Section { - internal var headerView: SectionSupplementalyView? - internal var footerView: SectionSupplementalyView? - internal var contents: [RowRepresentation] = [] - open var selectedAction: ((UITableView, IndexPath, Any?) -> Void)? - - public init() {} - - public convenience init(@CellBuilder _ contents: () -> [RowRepresentation]) { - self.init() - self.contents = contents() - } - - public convenience init(_ contents: [RowRepresentation]) { - self.init() - self.contents = contents - } - - @discardableResult - public func header(_ header: SectionSupplementalyView) -> Self { - headerView = header - return self - } - - @discardableResult - public func footer(_ footer: SectionSupplementalyView) -> Self { - footerView = footer - return self - } - - @discardableResult - public func contents(_ sectionContents: [RowRepresentation]) -> Self { - contents = sectionContents - return self - } - - @discardableResult - public func contents(@CellBuilder _ sectionContents: () -> [RowRepresentation]) -> Self { - contents = sectionContents() - return self - } - - @discardableResult - public func contents(_ closure: (Section) -> Void) -> Self { - closure(self) - return self - } - - @discardableResult - public func append(_ content: Content) -> Content { - contents.append(content) - return content - } - - @discardableResult - public func didSelect(_ action: @escaping (UITableView, IndexPath, Any?) -> Void) -> Self { - selectedAction = action - return self - } -} diff --git a/TableViewContent/Classes/Section/Section.swift b/TableViewContent/Classes/Section/Section.swift new file mode 100644 index 0000000..cb9cca8 --- /dev/null +++ b/TableViewContent/Classes/Section/Section.swift @@ -0,0 +1,87 @@ +// +// TableViewSection.swift +// Pods-TableViewContent_Example +// +// Created by Akira Matsuda on 2019/06/19. +// + +import UIKit + +@_functionBuilder +public enum CellBuilder { + public static func buildBlock(_ items: RowRepresentation...) -> [RowRepresentation] { + items + } +} + +public typealias SectionViewRepresentation = UIView & SectionConfigurable + +open class Section { + open var selectedAction: ((UITableView, IndexPath) -> Void)? + public var updateAfterSelected: Bool = false + public var updateAnimation: UITableView.RowAnimation = .automatic + public var headerView: SectionSupplementalyView? + public var footerView: SectionSupplementalyView? + public var rows = [RowRepresentation]() + public var sectionIndexTitle: String? + + public init() {} + + public convenience init(@CellBuilder _ rows: () -> [RowRepresentation]) { + self.init() + self.rows = rows() + } + + public convenience init(_ rows: [RowRepresentation]) { + self.init() + self.rows = rows + } + + @discardableResult + open func sectionIndexTitle(_ title: String) -> Self { + sectionIndexTitle = title + return self + } + + @discardableResult + open func header(_ header: SectionSupplementalyView) -> Self { + headerView = header + return self + } + + @discardableResult + open func footer(_ footer: SectionSupplementalyView) -> Self { + footerView = footer + return self + } + + @discardableResult + open func rows(_ sectionContents: [RowRepresentation]) -> Self { + rows = sectionContents + return self + } + + @discardableResult + open func rows(@CellBuilder _ sectionContents: () -> [RowRepresentation]) -> Self { + rows = sectionContents() + return self + } + + @discardableResult + open func rows(_ closure: (Section) -> Void) -> Self { + closure(self) + return self + } + + @discardableResult + open func append(_ row: RowRepresentation) -> Self { + rows.append(row) + return self + } + + @discardableResult + open func didSelect(_ action: @escaping (UITableView, IndexPath) -> Void) -> Self { + selectedAction = action + return self + } +} diff --git a/TableViewContent/Classes/Section/SectionSupplementalyView.swift b/TableViewContent/Classes/Section/SectionSupplementalyView.swift new file mode 100644 index 0000000..751bb15 --- /dev/null +++ b/TableViewContent/Classes/Section/SectionSupplementalyView.swift @@ -0,0 +1,29 @@ +// +// SectionSupplementalyView.swift +// TableViewContent +// +// Created by Akira Matsuda on 2020/11/25. +// + +import UIKit + +public enum SectionSupplementalyView { + case title(String) + case nib(UINib, Any) + case view(UIView & SectionConfigurable, Any) + + var sectionView: UIView? { + switch self { + case .title: + return nil + case let .nib(nib, data): + let internalView = nib.instantiate(withOwner: nil, options: nil).first as? SectionViewRepresentation + internalView?.configure(data) + return internalView + case let .view(sectionView, data): + let internalView = sectionView + internalView.configure(data) + return internalView + } + } +} diff --git a/TableViewContent/Classes/Section/SelectionSection.swift b/TableViewContent/Classes/Section/SelectionSection.swift new file mode 100644 index 0000000..a176465 --- /dev/null +++ b/TableViewContent/Classes/Section/SelectionSection.swift @@ -0,0 +1,50 @@ +// +// SelectionSection.swift +// TableViewContent +// +// Created by Akira Matsuda on 2020/11/25. +// + +import UIKit + +open class SelectionSection: Section { + public var exclusive: Bool = false + public var destructive: Bool = false + + public convenience init(exclusive: Bool, destructive: Bool = false) { + self.init() + self.exclusive = exclusive + self.destructive = destructive + } + + override public init() { + super.init() + updateAnimation = .fade + _ = didSelect { _, _ in } + } + + override open func didSelect(_ action: @escaping (UITableView, IndexPath) -> Void) -> Self { + selectedAction = { [unowned self] tableView, indexPath in + action(tableView, indexPath) + if self.exclusive { + for (index, value) in rows.enumerated() where index != indexPath.row { + if let row = value as? CheckmarkRow { + row.checked = false + } + tableView.reloadRows(at: [IndexPath(row: index, section: indexPath.section)], with: updateAnimation) + } + } + if let row = self.rows[indexPath.row] as? CheckmarkRow { + if self.destructive { + row.checked = true + } + else { + row.checked.toggle() + } + } + tableView.reloadRows(at: [indexPath], with: updateAnimation) + tableView.deselectRow(at: indexPath, animated: true) + } + return self + } +} diff --git a/TableViewContent/Classes/SwitchRow.swift b/TableViewContent/Classes/SwitchRow.swift deleted file mode 100644 index 71f598f..0000000 --- a/TableViewContent/Classes/SwitchRow.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// SwitchRow.swift -// Pods -// -// Created by Akira Matsuda on 2020/11/05. -// - -import UIKit - -open class SwitchRow: RowRepresentation { - private var configureContent: (SwitchTableViewCell, IndexPath, Bool) -> Void = { _, _, _ in } - private var toggledAction: (Bool) -> Void = { _ in } - public var isOn: Bool - - public init(title: String, isOn: Bool = false) { - self.isOn = isOn - super.init(SwitchTableViewCell.self, reuseIdentifier: NSStringFromClass(SwitchTableViewCell.self), data: isOn) - self.title(title) - super.configure(SwitchTableViewCell.self) { [unowned self] cell, indexPath, data in - cell.textLabel?.text = self.title - cell.detailTextLabel?.text = self.detailText - cell.imageView?.image = self.image - cell.selectionStyle = self.selectionStyle - cell.accessoryType = self.accessoryType - cell.editingAccessoryView = self.editingAccessoryView - cell.editingAccessoryType = self.editingAccessoryType - cell.isOn = self.isOn - cell.configure { [weak self] newValue in - guard let weakSelf = self else { - return - } - weakSelf.isOn = newValue - weakSelf.toggledAction(newValue) - } - self.configureContent(cell, indexPath, data as! Bool) - } - } - - @discardableResult - open func configure(_ configuration: @escaping (SwitchTableViewCell, IndexPath, Bool) -> Void) -> Self { - configureContent = configuration - return self - } - - @discardableResult - open func toggled(_ toggleAction: @escaping (Bool) -> Void) -> Self { - toggledAction = toggleAction - return self - } -} - -open class SwitchTableViewCell: UITableViewCell { - private let sw = UISwitch() - private var targetAdded = false - public var isOn: Bool { - get { - sw.isOn - } - set(isOn) { - sw.isOn = isOn - } - } - - private var toggledAction: ((Bool) -> Void)? - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - accessoryView = sw - selectionStyle = .none - sw.addTarget(self, action: #selector(toggled(_:)), for: .valueChanged) - } - - @available(*, unavailable) - public required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc - private func toggled(_ sender: UISwitch) { - toggledAction?(sender.isOn) - } - - func configure(action: ((Bool) -> Void)?) { - toggledAction = action - } -} diff --git a/run_formatter.sh b/run_formatter.sh new file mode 100755 index 0000000..f711ae1 --- /dev/null +++ b/run_formatter.sh @@ -0,0 +1,2 @@ +#!/bin/zsh +./Example/Pods/SwiftFormat/CommandLineTool/swiftformat .