Compare commits

..

41 Commits

Author SHA1 Message Date
Ivan Smolin fec9537745 up swift version to 5.7 2024-01-30 23:09:39 +03:00
Ivan Smolin 246c0d06c0 Merge pull request 'remove outdated actions and swift tools version' (#2) from feature/remote_outdated_code into master
Reviewed-on: #2
2024-01-30 22:27:14 +03:00
Ivan Smolin 54bf141aff remove outdated actions and swift tools version 2024-01-30 22:24:27 +03:00
Ivan Smolin 01134b83b4 update source url 2024-01-30 22:13:02 +03:00
Ivan Smolin 3fefa09c3a Merge pull request 'fix warnings' (#1) from fix/xcode_15 into master
Reviewed-on: #1
2024-01-30 22:08:00 +03:00
Ivan Smolin 4886e43d20 fix warnings 2024-01-30 22:01:31 +03:00
Loupehope 41826e18db
Merge pull request #6 from TouchInstinct/feature/actionRows
Replace UITableViewRowAction with UIContextualAction
2020-12-26 15:37:30 +03:00
Vlad caec2dd10e Return deprecated 2020-12-26 15:22:00 +03:00
Vlad efc03d7c22 Add performsFirstActionWithFullSwipe 2020-12-26 11:24:59 +03:00
Vlad 94c40faa63 Code correction 2020-12-26 11:05:20 +03:00
Vlad d87a23587e Add backward capability to editingActions 2020-12-26 10:52:06 +03:00
Vlad 49b3f868f3 Return IPHONEOS_DEPLOYMENT_TARGET 2020-12-26 10:36:32 +03:00
Vlad 37482d3b69 Replace UITableViewRowAction with UIContextualAction 2020-12-26 10:31:38 +03:00
Loupehope 2cc161f0c0
Merge pull request #5 from maxsokolov/master
Merge source master
2020-12-26 10:16:07 +03:00
Max Sokolov 8bf4840d9d bump to 2.11.0 2020-05-04 08:11:57 -04:00
Max Sokolov 3b266fb7c7
Merge pull request #98 from mrtokii/feature/ios13_actions
Add support for iOS 13 actions
2020-05-04 08:06:36 -04:00
mrtokii eb93fe253c Add multiple selection actions & context menu action 2020-05-03 16:25:30 +03:00
Max Sokolov 44c55d2f05
Merge pull request #96 from DmitryFrishbuter/readme-patch
Fix typo in README.md
2019-10-05 15:10:11 -04:00
Dmitry Frishbuter efe99eeb46 Fix typo in README.md 2019-10-05 21:57:35 +06:00
Max Sokolov a0658f0b2e fix travis-ci 2019-09-29 10:03:45 -04:00
Max Sokolov 065cd9ace3 fix travis-ci 2019-09-29 09:56:11 -04:00
Max Sokolov c3652eec6f bump podspec 2019-09-29 09:51:33 -04:00
Max Sokolov 01bf1e01e3 support swift 5.1 and xcode 11 2019-09-29 09:50:40 -04:00
Max Sokolov 45aee4522d
Merge pull request #95 from bellebethcooper/master
Updated Swift tools version and added missing requirements
2019-09-19 16:45:06 -04:00
bellebethcooper f39e7ac602
Updated Swift tools version and added missing requirements 2019-09-07 15:49:07 +10:00
Max Sokolov dc1a924a80 drop iOS 9 checks on ci 2019-06-25 14:45:02 +03:00
Max Sokolov 402757c41f drop iOS 9 checks on ci 2019-06-25 14:36:47 +03:00
Max Sokolov d6520346c3
Merge pull request #94 from dmertsalov/master
Add accessoryButton action support
2019-05-29 09:34:05 +03:00
Denis 959032a03f Add accessoryButton action support 2019-05-27 11:01:35 +03:00
Max Sokolov 00de0cd809
Merge pull request #93 from IntelBohdan/master
added: willDeselect
2019-05-10 19:35:09 +03:00
Bogdan Kurpakov 337175c507 added: willDeselect 2019-05-09 11:10:31 +02:00
Max Sokolov a4bacd2c16 fix travis ci 2019-04-04 23:40:25 +03:00
Max Sokolov 603264a6d1 fix travis ci 2019-04-04 23:31:40 +03:00
Max Sokolov 1d4fdaad0d update readme 2019-04-04 23:27:25 +03:00
Max Sokolov f5ad500ba4 support swift 5.0, bump to 2.9.0 2019-04-04 23:26:15 +03:00
Ivan Zinovyev 1b4a988b35
Merge pull request #4 from TouchInstinct/expandable_states
Expandable states
2019-01-22 13:06:16 +03:00
Ivan Zinovyev f8f2ca6852 Fix indentation 2019-01-22 03:16:40 +03:00
Ivan Zinovyev ff18a4d8b8 Add Expandable States 2019-01-22 03:00:41 +03:00
Ivan Zinovyev d098691621 Merge remote-tracking branch 'maxsokolov/master' into expandable_states 2019-01-22 02:51:16 +03:00
Max Sokolov 8b6319d510 bump to 2.8.1 2019-01-10 18:35:53 +03:00
Max Sokolov b049864758 fix crash on iOS 10.3.1 2019-01-10 18:35:35 +03:00
15 changed files with 297 additions and 121 deletions

View File

@ -1 +1 @@
4.2 5.7

View File

@ -1,5 +1,5 @@
language: objective-c language: objective-c
osx_image: xcode10 osx_image: xcode11
branches: branches:
only: only:
- master - master
@ -7,17 +7,15 @@ env:
global: global:
- LC_CTYPE=en_US.UTF-8 - LC_CTYPE=en_US.UTF-8
- LANG=en_US.UTF-8 - LANG=en_US.UTF-8
- IOS_SDK=iphonesimulator12.0 - IOS_SDK=iphonesimulator13.0
- SCHEME_IOS="TableKit" - SCHEME_IOS="TableKit"
- PROJECT_FRAMEWORK="TableKit.xcodeproj" - PROJECT_FRAMEWORK="TableKit.xcodeproj"
matrix: matrix:
- DESTINATION="OS=9.0,name=iPhone 6" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK" - DESTINATION="OS=10.3.1,name=iPhone 5" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=9.1,name=iPhone 6 Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK" - DESTINATION="OS=11.1,name=iPhone 6" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=9.2,name=iPhone 6S" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=9.3,name=iPhone 6S Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=10.0,name=iPhone 5" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=12.0,name=iPhone 7 Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK" - DESTINATION="OS=12.0,name=iPhone 7 Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=13.0,name=iPhone 11" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
script: script:
- set -o pipefail - set -o pipefail

View File

@ -2,6 +2,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [2.10.0](https://github.com/maxsokolov/TableKit/releases/tag/2.10.0)
Released on 2019-09-29.
- Swift 5.1 support.
## [2.9.0](https://github.com/maxsokolov/TableKit/releases/tag/2.9.0)
Released on 2019-04-04.
- Swift 5.0 support.
## [2.8.0](https://github.com/maxsokolov/TableKit/releases/tag/2.8.0) ## [2.8.0](https://github.com/maxsokolov/TableKit/releases/tag/2.8.0)
Released on 2018-09-30. Released on 2018-09-30.
- Swift 4.2 support. - Swift 4.2 support.

View File

@ -248,6 +248,7 @@
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
Base, Base,
); );
@ -377,7 +378,7 @@
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -425,7 +426,7 @@
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 5.0;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;
@ -443,7 +444,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo; PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
PRODUCT_NAME = TableKitDemo; PRODUCT_NAME = TableKitDemo;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -460,7 +461,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo; PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
PRODUCT_NAME = TableKitDemo; PRODUCT_NAME = TableKitDemo;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };

View File

@ -1,5 +1,19 @@
// swift-tools-version:5.7
import PackageDescription import PackageDescription
let package = Package( let package = Package(
name: "TableKit" name: "TableKit",
)
products: [
.library(
name: "TableKit",
targets: ["TableKit"]),
],
targets: [
.target(
name: "TableKit",
path: "Sources")
]
)

View File

@ -2,9 +2,9 @@
<p align="left"> <p align="left">
<a href="https://travis-ci.org/maxsokolov/TableKit"><img src="https://api.travis-ci.org/maxsokolov/TableKit.svg" alt="Build Status" /></a> <a href="https://travis-ci.org/maxsokolov/TableKit"><img src="https://api.travis-ci.org/maxsokolov/TableKit.svg" alt="Build Status" /></a>
<a href="https://developer.apple.com/swift"><img src="https://img.shields.io/badge/Swift_4.2-compatible-4BC51D.svg?style=flat" alt="Swift 4.2 compatible" /></a> <a href="https://developer.apple.com/swift"><img src="https://img.shields.io/badge/Swift_5.1-compatible-4BC51D.svg?style=flat" alt="Swift 5.1 compatible" /></a>
<a href="https://github.com/Carthage/Carthage"><img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat" alt="Carthage compatible" /></a> <a href="https://github.com/Carthage/Carthage"><img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat" alt="Carthage compatible" /></a>
<a href="https://cocoapods.org/pods/tablekit"><img src="https://img.shields.io/badge/pod-2.8.0-blue.svg" alt="CocoaPods compatible" /></a> <a href="https://cocoapods.org/pods/tablekit"><img src="https://img.shields.io/badge/pod-2.11.0-blue.svg" alt="CocoaPods compatible" /></a>
<img src="https://img.shields.io/badge/platform-iOS-blue.svg?style=flat" alt="Platform iOS" /> <img src="https://img.shields.io/badge/platform-iOS-blue.svg?style=flat" alt="Platform iOS" />
<a href="https://raw.githubusercontent.com/maxsokolov/tablekit/master/LICENSE"><img src="http://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="License: MIT" /></a> <a href="https://raw.githubusercontent.com/maxsokolov/tablekit/master/LICENSE"><img src="http://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="License: MIT" /></a>
</p> </p>
@ -204,7 +204,7 @@ tableDirector += rows
Done, your table is ready. Done, your table is ready.
## Automatic cell registration ## Automatic cell registration
TableKit can register your cells in a table view automatically. In case if your reusable cell id mathces cell's xib name: TableKit can register your cells in a table view automatically. In case if your reusable cell id matches cell's xib name:
```ruby ```ruby
MyTableViewCell.swift MyTableViewCell.swift

View File

@ -1,70 +1,96 @@
import UIKit import UIKit
public extension TimeInterval {
static let defaultExpandableAnimationDuration: TimeInterval = 0.3
}
public protocol Expandable { public protocol Expandable {
associatedtype ViewModelType: ExpandableCellViewModel associatedtype ViewModelType: ExpandableCellViewModel
var viewModel: ViewModelType? { get } var viewModel: ViewModelType? { get }
func configureAppearance(isCollapsed: Bool) func configure(state: ExpandableState)
} }
extension Expandable where Self: UITableViewCell & ConfigurableCell { extension Expandable where Self: UITableViewCell & ConfigurableCell {
public func initState() { public func initState() {
guard let viewModel = viewModel else { guard let viewModel = viewModel else {
return return
} }
changeState(isCollapsed: viewModel.isCollapsed) changeState(expandableState: viewModel.expandableState)
} }
private func changeState(isCollapsed: Bool) { private func changeState(expandableState: ExpandableState) {
// layout to get right frames, frame of bottom subview can be used to get expanded height // layout to get right frames, frame of bottom subview can be used to get expanded height
setNeedsLayout()
layoutIfNeeded() layoutIfNeeded()
// apply changes // apply changes
configureAppearance(isCollapsed: isCollapsed) configure(state: expandableState)
layoutIfNeeded() layoutIfNeeded()
} }
public func toggleState(animated: Bool = true, public func toggleState(animated: Bool = true,
animationDuration: TimeInterval = 0.3) { animationDuration: TimeInterval = .defaultExpandableAnimationDuration) {
guard let tableView = tableView, guard let viewModel = viewModel,
let viewModel = viewModel else { let stateIndex = viewModel.availableStates.firstIndex(where: { $0 == viewModel.expandableState }) else {
return return
} }
let targetState = stateIndex == viewModel.availableStates.count - 1
? viewModel.availableStates[0]
: viewModel.availableStates[stateIndex + 1]
transition(to: targetState,
animated: animated,
animationDuration: animationDuration)
}
public func transition(to state: ExpandableState,
animated: Bool = true,
animationDuration: TimeInterval = .defaultExpandableAnimationDuration) {
guard let tableView = tableView,
let viewModel = viewModel,
viewModel.expandableState != state else {
return
}
let contentOffset = tableView.contentOffset let contentOffset = tableView.contentOffset
if animated { if animated {
UIView.animate(withDuration: animationDuration, UIView.animate(withDuration: animationDuration,
animations: { [weak self] in animations: { [weak self] in
self?.applyChanges(isCollapsed: !viewModel.isCollapsed) self?.applyChanges(expandableState: state)
}, completion: { _ in }, completion: { _ in
viewModel.isCollapsed.toggle() viewModel.expandableState = state
}) })
} else { } else {
applyChanges(isCollapsed: !viewModel.isCollapsed) applyChanges(expandableState: state)
viewModel.isCollapsed.toggle() viewModel.expandableState = state
} }
tableView.beginUpdates() tableView.beginUpdates()
tableView.endUpdates() tableView.endUpdates()
tableView.setContentOffset(contentOffset, animated: false) tableView.setContentOffset(contentOffset, animated: false)
} }
private func applyChanges(isCollapsed: Bool) { public func applyChanges(expandableState: ExpandableState) {
changeState(isCollapsed: isCollapsed) changeState(expandableState: expandableState)
if let indexPath = indexPath, if let indexPath = indexPath,
let tableDirector = (tableView?.delegate as? TableDirector), let tableDirector = (tableView?.delegate as? TableDirector),
let cellHeightCalculator = tableDirector.rowHeightCalculator as? ExpandableCellHeightCalculator { let cellHeightCalculator = tableDirector.rowHeightCalculator as? ExpandableCellHeightCalculator {
cellHeightCalculator.updateCached(height: height(layoutType: Self.layoutType), for: indexPath) cellHeightCalculator.updateCached(height: expandableState.height ?? height(layoutType: Self.layoutType), for: indexPath)
} }
} }
} }

View File

@ -1,5 +1,15 @@
public protocol ExpandableCellViewModel: class { public protocol ExpandableCellViewModel: AnyObject {
var isCollapsed: Bool { get set } var expandableState: ExpandableState { get set }
var availableStates: [ExpandableState] { get }
}
public extension ExpandableCellViewModel {
var availableStates: [ExpandableState] {
return [.collapsed, .expanded]
}
} }

View File

@ -0,0 +1,41 @@
import UIKit
public enum ExpandableState {
case collapsed
case expanded
case height(value: CGFloat)
}
extension ExpandableState: Equatable { }
extension ExpandableState {
public var isCollapsed: Bool {
guard case .collapsed = self else {
return false
}
return true
}
public var isExpanded: Bool {
guard case .expanded = self else {
return false
}
return true
}
public var height: CGFloat? {
guard case let .height(value: height) = self else {
return nil
}
return height
}
}

View File

@ -95,6 +95,14 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
tableView?.reloadData() tableView?.reloadData()
} }
// MARK: - Private
private func row(at indexPath: IndexPath) -> Row? {
if indexPath.section < sections.count && indexPath.row < sections[indexPath.section].rows.count {
return sections[indexPath.section].rows[indexPath.row]
}
return nil
}
// MARK: Public // MARK: Public
@discardableResult @discardableResult
open func invoke( open func invoke(
@ -102,15 +110,13 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
cell: UITableViewCell?, indexPath: IndexPath, cell: UITableViewCell?, indexPath: IndexPath,
userInfo: [AnyHashable: Any]? = nil) -> Any? userInfo: [AnyHashable: Any]? = nil) -> Any?
{ {
if indexPath.section < sections.count && indexPath.row < sections[indexPath.section].rows.count { guard let row = row(at: indexPath) else { return nil }
return sections[indexPath.section].rows[indexPath.row].invoke( return row.invoke(
action: action, action: action,
cell: cell, cell: cell,
path: indexPath, path: indexPath,
userInfo: userInfo userInfo: userInfo
) )
}
return nil
} }
open override func responds(to selector: Selector) -> Bool { open override func responds(to selector: Selector) -> Bool {
@ -125,7 +131,8 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
// MARK: - Internal // MARK: - Internal
func hasAction(_ action: TableRowActionType, atIndexPath indexPath: IndexPath) -> Bool { func hasAction(_ action: TableRowActionType, atIndexPath indexPath: IndexPath) -> Bool {
return sections[indexPath.section].rows[indexPath.row].has(action: action) guard let row = row(at: indexPath) else { return false }
return row.has(action: action)
} }
@objc @objc
@ -172,9 +179,7 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
} }
open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard !sections.isEmpty else { guard section < sections.count else { return 0 }
return 0
}
return sections[section].numberOfRows return sections[section].numberOfRows
} }
@ -200,51 +205,39 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
// MARK: UITableViewDataSource - section setup // MARK: UITableViewDataSource - section setup
open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard !sections.isEmpty else { guard section < sections.count else { return nil }
return nil
}
return sections[section].headerTitle return sections[section].headerTitle
} }
open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
guard !sections.isEmpty else { guard section < sections.count else { return nil }
return nil
}
return sections[section].footerTitle return sections[section].footerTitle
} }
// MARK: UITableViewDelegate - section setup // MARK: UITableViewDelegate - section setup
open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard !sections.isEmpty else { guard section < sections.count else { return nil }
return nil
}
return sections[section].headerView return sections[section].headerView
} }
open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
guard !sections.isEmpty else { guard section < sections.count else { return nil }
return nil
}
return sections[section].footerView return sections[section].footerView
} }
open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
guard !sections.isEmpty else { guard section < sections.count else { return 0 }
return UITableView.automaticDimension
}
let section = sections[section] let section = sections[section]
return section.headerHeight ?? section.headerView?.frame.size.height ?? UITableView.automaticDimension return section.headerHeight ?? section.headerView?.frame.size.height ?? UITableView.automaticDimension
} }
open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
guard !sections.isEmpty else { guard section < sections.count else { return 0 }
return UITableView.automaticDimension
}
let section = sections[section] let section = sections[section]
return section.footerHeight return section.footerHeight
@ -316,13 +309,62 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
return indexPath return indexPath
} }
open func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? {
if hasAction(.willDeselect, atIndexPath: indexPath) {
return invoke(action: .willDeselect, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? IndexPath
}
return indexPath
}
@available(iOS 13.0, *)
open func tableView(
_ tableView: UITableView,
shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool
{
invoke(action: .shouldBeginMultipleSelection, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? false
}
@available(iOS 13.0, *)
open func tableView(
_ tableView: UITableView,
didBeginMultipleSelectionInteractionAt indexPath: IndexPath)
{
invoke(action: .didBeginMultipleSelection, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath)
}
@available(iOS 13.0, *)
open func tableView(
_ tableView: UITableView,
contextMenuConfigurationForRowAt indexPath: IndexPath,
point: CGPoint) -> UIContextMenuConfiguration?
{
invoke(action: .showContextMenu, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath, userInfo: [TableKitUserInfoKeys.ContextMenuInvokePoint: point]) as? UIContextMenuConfiguration
}
// MARK: - Row editing // MARK: - Row editing
open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return sections[indexPath.section].rows[indexPath.row].isEditingAllowed(forIndexPath: indexPath) return sections[indexPath.section].rows[indexPath.row].isEditingAllowed(forIndexPath: indexPath)
} }
open func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { open func tableView(_ tableView: UITableView,
return sections[indexPath.section].rows[indexPath.row].editingActions leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let currentRow = sections[indexPath.section].rows[indexPath.row]
let configuration = UISwipeActionsConfiguration(actions: currentRow.leadingContextualActions)
configuration.performsFirstActionWithFullSwipe = currentRow.performsFirstActionWithFullSwipe
return configuration
}
open func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let currentRow = sections[indexPath.section].rows[indexPath.row]
let configuration = UISwipeActionsConfiguration(actions: currentRow.trailingContextualActions)
configuration.performsFirstActionWithFullSwipe = currentRow.performsFirstActionWithFullSwipe
return configuration
} }
open func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { open func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
@ -354,42 +396,47 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
open func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { open func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
invoke(action: .move, cell: tableView.cellForRow(at: sourceIndexPath), indexPath: sourceIndexPath, userInfo: [TableKitUserInfoKeys.CellMoveDestinationIndexPath: destinationIndexPath]) invoke(action: .move, cell: tableView.cellForRow(at: sourceIndexPath), indexPath: sourceIndexPath, userInfo: [TableKitUserInfoKeys.CellMoveDestinationIndexPath: destinationIndexPath])
} }
open func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
invoke(action: .accessoryButtonTap, cell: cell, indexPath: indexPath)
}
} }
// MARK: - Sections manipulation // MARK: - Sections manipulation
extension TableDirector { extension TableDirector {
@discardableResult @discardableResult
open func append(section: TableSection) -> Self { public func append(section: TableSection) -> Self {
append(sections: [section]) append(sections: [section])
return self return self
} }
@discardableResult @discardableResult
open func append(sections: [TableSection]) -> Self { public func append(sections: [TableSection]) -> Self {
self.sections.append(contentsOf: sections) self.sections.append(contentsOf: sections)
return self return self
} }
@discardableResult @discardableResult
open func append(rows: [Row]) -> Self { public func append(rows: [Row]) -> Self {
append(section: TableSection(rows: rows)) append(section: TableSection(rows: rows))
return self return self
} }
@discardableResult @discardableResult
open func insert(section: TableSection, atIndex index: Int) -> Self { public func insert(section: TableSection, atIndex index: Int) -> Self {
sections.insert(section, at: index) sections.insert(section, at: index)
return self return self
} }
@discardableResult @discardableResult
open func replaceSection(at index: Int, with section: TableSection) -> Self { public func replaceSection(at index: Int, with section: TableSection) -> Self {
if index < sections.count { if index < sections.count {
sections[index] = section sections[index] = section
} }
@ -397,20 +444,20 @@ extension TableDirector {
} }
@discardableResult @discardableResult
open func delete(sectionAt index: Int) -> Self { public func delete(sectionAt index: Int) -> Self {
sections.remove(at: index) sections.remove(at: index)
return self return self
} }
@discardableResult @discardableResult
open func remove(sectionAt index: Int) -> Self { public func remove(sectionAt index: Int) -> Self {
return delete(sectionAt: index) return delete(sectionAt: index)
} }
@discardableResult @discardableResult
open func clear() -> Self { public func clear() -> Self {
rowHeightCalculator?.invalidate() rowHeightCalculator?.invalidate()
sections.removeAll() sections.removeAll()
@ -420,7 +467,7 @@ extension TableDirector {
// MARK: - deprecated methods // MARK: - deprecated methods
@available(*, deprecated, message: "Use 'delete(sectionAt:)' method instead") @available(*, deprecated, message: "Use 'delete(sectionAt:)' method instead")
@discardableResult @discardableResult
open func delete(index: Int) -> Self { public func delete(index: Int) -> Self {
sections.remove(at: index) sections.remove(at: index)
return self return self

View File

@ -27,6 +27,7 @@ struct TableKitNotifications {
public struct TableKitUserInfoKeys { public struct TableKitUserInfoKeys {
public static let CellMoveDestinationIndexPath = "TableKitCellMoveDestinationIndexPath" public static let CellMoveDestinationIndexPath = "TableKitCellMoveDestinationIndexPath"
public static let CellCanMoveProposedIndexPath = "CellCanMoveProposedIndexPath" public static let CellCanMoveProposedIndexPath = "CellCanMoveProposedIndexPath"
public static let ContextMenuInvokePoint = "ContextMenuInvokePoint"
} }
public protocol RowConfigurable { public protocol RowConfigurable {
@ -36,8 +37,10 @@ public protocol RowConfigurable {
} }
public protocol RowActionable { public protocol RowActionable {
var leadingContextualActions: [UIContextualAction] { get }
var editingActions: [UITableViewRowAction]? { get } var trailingContextualActions: [UIContextualAction] { get }
var performsFirstActionWithFullSwipe: Bool { get }
func isEditingAllowed(forIndexPath indexPath: IndexPath) -> Bool func isEditingAllowed(forIndexPath indexPath: IndexPath) -> Bool
func invoke( func invoke(
@ -71,9 +74,12 @@ public enum TableRowActionType {
case select case select
case deselect case deselect
case willSelect case willSelect
case willDeselect
case willDisplay case willDisplay
case didEndDisplaying case didEndDisplaying
case shouldHighlight case shouldHighlight
case shouldBeginMultipleSelection
case didBeginMultipleSelection
case height case height
case canEdit case canEdit
case configure case configure
@ -81,6 +87,8 @@ public enum TableRowActionType {
case canMove case canMove
case canMoveTo case canMoveTo
case move case move
case showContextMenu
case accessoryButtonTap
case custom(String) case custom(String)
var key: String { var key: String {

View File

@ -21,10 +21,21 @@
import UIKit import UIKit
open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableViewCell { open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableViewCell {
public let item: CellType.CellData public let item: CellType.CellData
private lazy var actions = [String: [TableRowAction<CellType>]]() private lazy var actions = [String: [TableRowAction<CellType>]]()
private(set) open var editingActions: [UITableViewRowAction]?
open var leadingContextualActions: [UIContextualAction] {
[]
}
open var trailingContextualActions: [UIContextualAction] {
[]
}
open var performsFirstActionWithFullSwipe: Bool {
false
}
open var hashValue: Int { open var hashValue: Int {
return ObjectIdentifier(self).hashValue return ObjectIdentifier(self).hashValue
@ -50,10 +61,11 @@ open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableView
return CellType.self return CellType.self
} }
public init(item: CellType.CellData, actions: [TableRowAction<CellType>]? = nil, editingActions: [UITableViewRowAction]? = nil) { public init(item: CellType.CellData,
actions: [TableRowAction<CellType>]? = nil) {
self.item = item self.item = item
self.editingActions = editingActions
actions?.forEach { on($0) } actions?.forEach { on($0) }
} }
@ -81,7 +93,10 @@ open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableView
if actions[TableRowActionType.canEdit.key] != nil { if actions[TableRowActionType.canEdit.key] != nil {
return invoke(action: .canEdit, cell: nil, path: indexPath) as? Bool ?? false return invoke(action: .canEdit, cell: nil, path: indexPath) as? Bool ?? false
} }
return editingActions?.isEmpty == false || actions[TableRowActionType.clickDelete.key] != nil
return !leadingContextualActions.isEmpty
|| !trailingContextualActions.isEmpty
|| actions[TableRowActionType.clickDelete.key] != nil
} }
// MARK: - actions - // MARK: - actions -
@ -117,7 +132,7 @@ open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableView
open func removeAction(forActionId actionId: String) { open func removeAction(forActionId actionId: String) {
for (key, value) in actions { for (key, value) in actions {
if let actionIndex = value.index(where: { $0.id == actionId }) { if let actionIndex = value.firstIndex(where: { $0.id == actionId }) {
actions[key]?.remove(at: actionIndex) actions[key]?.remove(at: actionIndex)
} }
} }

View File

@ -2,16 +2,16 @@ Pod::Spec.new do |s|
s.name = 'TableKit' s.name = 'TableKit'
s.module_name = 'TableKit' s.module_name = 'TableKit'
s.version = '2.8.0' s.version = '2.12'
s.homepage = 'https://github.com/maxsokolov/TableKit' s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/TableKit'
s.summary = 'Type-safe declarative table views with Swift.' s.summary = 'Type-safe declarative table views with Swift.'
s.author = { 'Max Sokolov' => 'i@maxsokolov.net' } s.author = { 'Max Sokolov' => 'i@maxsokolov.net' }
s.license = { :type => 'MIT', :file => 'LICENSE' } s.license = { :type => 'MIT', :file => 'LICENSE' }
s.platforms = { :ios => '8.0' } s.platforms = { :ios => '12.0' }
s.ios.deployment_target = '8.0' s.ios.deployment_target = '12.0'
s.source_files = 'Sources/*.swift' s.source_files = 'Sources/*.swift'
s.source = { :git => 'https://github.com/maxsokolov/TableKit.git', :tag => s.version } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/TableKit.git', :tag => s.version }
end end

View File

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
2CBFA2F521F692F100147B56 /* ExpandableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBFA2F421F692F100147B56 /* ExpandableState.swift */; };
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */; }; 3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */; };
3201E78621BE9E25001DF9E7 /* UITableViewCell+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.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 */; }; 3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78721BE9EB2001DF9E7 /* Expandable.swift */; };
@ -37,6 +38,7 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
2CBFA2F421F692F100147B56 /* ExpandableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableState.swift; sourceTree = "<group>"; };
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellHeightCalculator.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 3201E78721BE9EB2001DF9E7 /* Expandable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expandable.swift; sourceTree = "<group>"; };
@ -115,6 +117,7 @@
3201E78721BE9EB2001DF9E7 /* Expandable.swift */, 3201E78721BE9EB2001DF9E7 /* Expandable.swift */,
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */, 3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */,
32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */, 32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */,
2CBFA2F421F692F100147B56 /* ExpandableState.swift */,
); );
path = Sources; path = Sources;
sourceTree = "<group>"; sourceTree = "<group>";
@ -210,6 +213,7 @@
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
); );
mainGroup = DA9EA74C1D0B679A0021F650; mainGroup = DA9EA74C1D0B679A0021F650;
@ -250,6 +254,7 @@
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */, DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */,
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */, DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */,
3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */, 3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */,
2CBFA2F521F692F100147B56 /* ExpandableState.swift in Sources */,
DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */, DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */,
DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */, DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */,
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */, 3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */,
@ -335,7 +340,7 @@
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = ""; VERSION_INFO_PREFIX = "";
@ -388,7 +393,7 @@
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
@ -413,7 +418,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -433,7 +438,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKit; PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKit;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@ -445,7 +450,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests; PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -457,7 +462,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests; PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };

View File

@ -74,7 +74,10 @@ class TableKitTests: XCTestCase {
super.setUp() super.setUp()
testController = TestController() testController = TestController()
testController.view.isHidden = false testController.tableView.frame = UIScreen.main.bounds
testController.tableView.isHidden = false
testController.tableView.setNeedsLayout()
testController.tableView.layoutIfNeeded()
} }
override func tearDown() { override func tearDown() {