Compare commits
234 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
fec9537745 | |
|
|
246c0d06c0 | |
|
|
54bf141aff | |
|
|
01134b83b4 | |
|
|
3fefa09c3a | |
|
|
4886e43d20 | |
|
|
41826e18db | |
|
|
caec2dd10e | |
|
|
efc03d7c22 | |
|
|
94c40faa63 | |
|
|
d87a23587e | |
|
|
49b3f868f3 | |
|
|
37482d3b69 | |
|
|
2cc161f0c0 | |
|
|
8bf4840d9d | |
|
|
3b266fb7c7 | |
|
|
eb93fe253c | |
|
|
44c55d2f05 | |
|
|
efe99eeb46 | |
|
|
a0658f0b2e | |
|
|
065cd9ace3 | |
|
|
c3652eec6f | |
|
|
01bf1e01e3 | |
|
|
45aee4522d | |
|
|
f39e7ac602 | |
|
|
dc1a924a80 | |
|
|
402757c41f | |
|
|
d6520346c3 | |
|
|
959032a03f | |
|
|
00de0cd809 | |
|
|
337175c507 | |
|
|
a4bacd2c16 | |
|
|
603264a6d1 | |
|
|
1d4fdaad0d | |
|
|
f5ad500ba4 | |
|
|
1b4a988b35 | |
|
|
f8f2ca6852 | |
|
|
ff18a4d8b8 | |
|
|
d098691621 | |
|
|
8b6319d510 | |
|
|
b049864758 | |
|
|
7f349c6944 | |
|
|
a768352b47 | |
|
|
65cf31717b | |
|
|
9d10bc18bf | |
|
|
497b4e009c | |
|
|
6ef5ad504e | |
|
|
e22ec03990 | |
|
|
5982d5db3a | |
|
|
6eaf2cf3a2 | |
|
|
921e2b42b3 | |
|
|
70e6addcd0 | |
|
|
86f07b8e7c | |
|
|
1c92c14a1a | |
|
|
7416271076 | |
|
|
0812293813 | |
|
|
f16a5a29c9 | |
|
|
b379ac6272 | |
|
|
0c87f3ee88 | |
|
|
ce24369557 | |
|
|
85a45c33f2 | |
|
|
92b723b8ca | |
|
|
12883a33de | |
|
|
1d28233ebe | |
|
|
dd8e5d0625 | |
|
|
21aab6256f | |
|
|
cc02531b27 | |
|
|
b62dea8702 | |
|
|
279fdd4854 | |
|
|
02f77ed699 | |
|
|
74af45f3cd | |
|
|
f05511cfba | |
|
|
de5593f6a9 | |
|
|
a1e1e9d908 | |
|
|
70d83fc5b0 | |
|
|
23b51227fd | |
|
|
d8ae329118 | |
|
|
fd50b732c9 | |
|
|
709f739ab6 | |
|
|
9fff8c861c | |
|
|
da76188afd | |
|
|
e3870d5ce8 | |
|
|
a7488b1d4f | |
|
|
4712406618 | |
|
|
d595a5d2df | |
|
|
8463401b94 | |
|
|
05119aae43 | |
|
|
f3c7ea9e43 | |
|
|
ae24e5c7c0 | |
|
|
0c96110ca2 | |
|
|
ad979f4376 | |
|
|
6f849e4cae | |
|
|
293caeebdb | |
|
|
cc69951856 | |
|
|
898191b0fa | |
|
|
bd8ac5a28b | |
|
|
9ef7a7a8bb | |
|
|
ce2874c58d | |
|
|
c9bd52797d | |
|
|
06becd8624 | |
|
|
c277b6529c | |
|
|
39b4df6ee9 | |
|
|
b49b01ff59 | |
|
|
92b3e3b9a7 | |
|
|
1b97d33cf6 | |
|
|
721ba1972a | |
|
|
ef536b71b3 | |
|
|
d12f3549aa | |
|
|
155a4d1cb7 | |
|
|
6b25d54bd8 | |
|
|
9238ab8472 | |
|
|
78b67e4c42 | |
|
|
3821d4300c | |
|
|
065d8ccae3 | |
|
|
d4fc924676 | |
|
|
2b96888d12 | |
|
|
9bbd9c4b69 | |
|
|
44cbb8b7d8 | |
|
|
48a36e0969 | |
|
|
67003df587 | |
|
|
0e1aa0261f | |
|
|
3ba9f45ded | |
|
|
6abdc8ad8d | |
|
|
7132a0a548 | |
|
|
934e7623f9 | |
|
|
e74fee3653 | |
|
|
d5d785218a | |
|
|
443b8aaa62 | |
|
|
0f8d0db84e | |
|
|
f19b637999 | |
|
|
b307b2d809 | |
|
|
ecc90b1db8 | |
|
|
28f6a60d61 | |
|
|
1eed11abbf | |
|
|
162aee0033 | |
|
|
135bc22804 | |
|
|
f794dea7ee | |
|
|
a5d8f0874a | |
|
|
8991138f18 | |
|
|
becdc205bb | |
|
|
c3d6f0af60 | |
|
|
b449e05edc | |
|
|
db6dc40fac | |
|
|
e18c6ef669 | |
|
|
b74403e2c6 | |
|
|
5f6de4703a | |
|
|
c040613654 | |
|
|
0af6956d62 | |
|
|
3da097f1f1 | |
|
|
3de586c4f6 | |
|
|
7f65f0c4e0 | |
|
|
9ae69b71ca | |
|
|
a240acee2d | |
|
|
9e1372252a | |
|
|
5a7a9b9655 | |
|
|
7eca499a59 | |
|
|
8de932b1db | |
|
|
be410f0cc3 | |
|
|
6601b14b7f | |
|
|
da98b9892f | |
|
|
04e62a8bc0 | |
|
|
9ba8ef4932 | |
|
|
24d3540dd5 | |
|
|
6f441b2f7d | |
|
|
38230a9a57 | |
|
|
676d4fdf88 | |
|
|
262d2b83e2 | |
|
|
a88043b33a | |
|
|
5db9726dab | |
|
|
81fbd2f5e5 | |
|
|
0b46f7c52f | |
|
|
67a41b062f | |
|
|
edcbcf60b7 | |
|
|
6e2b707df7 | |
|
|
a25e4c0eb4 | |
|
|
bfc59a0670 | |
|
|
3751eec584 | |
|
|
0916b5f990 | |
|
|
24a7f0589f | |
|
|
3f88b6d0aa | |
|
|
6c73ef4860 | |
|
|
c09e06f67d | |
|
|
6a1650a68b | |
|
|
eebaaf5268 | |
|
|
29202d4405 | |
|
|
0f93d296ed | |
|
|
1be65d18ad | |
|
|
70d6cb8d73 | |
|
|
aeae61d48b | |
|
|
1df6447d73 | |
|
|
9218051f10 | |
|
|
52e44dcc6e | |
|
|
90aebed304 | |
|
|
40d138eac1 | |
|
|
7a310f6db2 | |
|
|
9a50eba12e | |
|
|
ddced231c3 | |
|
|
155524f13c | |
|
|
39eda5a8f3 | |
|
|
85cf81ecd6 | |
|
|
99fc12e723 | |
|
|
60003d7557 | |
|
|
5e5643c342 | |
|
|
a7530eb3c7 | |
|
|
1ae302f7ab | |
|
|
f132560f39 | |
|
|
a0113c23e0 | |
|
|
520f1f91f0 | |
|
|
d3773efbcc | |
|
|
4dd21a7d2e | |
|
|
a9a2496831 | |
|
|
7a732cb7c3 | |
|
|
d42757fb68 | |
|
|
a9c5d997cb | |
|
|
b8084efc08 | |
|
|
ab6e88ad65 | |
|
|
0c527fd47f | |
|
|
7438856134 | |
|
|
cf37c2b5eb | |
|
|
f90db7de16 | |
|
|
0186da5452 | |
|
|
09d5d75cef | |
|
|
8477cea35b | |
|
|
aaafb0d28d | |
|
|
4b4d2a616d | |
|
|
b7398a2986 | |
|
|
762fd7546b | |
|
|
a5ff6c520b | |
|
|
d3ccd86577 | |
|
|
f23901d4a1 | |
|
|
663111500f | |
|
|
26192a89b8 | |
|
|
2e8b3f3232 | |
|
|
da51e2b7e9 |
|
|
@ -0,0 +1 @@
|
|||
5.7
|
||||
28
.travis.yml
28
.travis.yml
|
|
@ -1,10 +1,24 @@
|
|||
language: objective-c
|
||||
osx_image: xcode7.3
|
||||
osx_image: xcode11
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
before_install:
|
||||
- brew update
|
||||
- brew reinstall --HEAD xctool
|
||||
only:
|
||||
- master
|
||||
env:
|
||||
global:
|
||||
- LC_CTYPE=en_US.UTF-8
|
||||
- LANG=en_US.UTF-8
|
||||
- IOS_SDK=iphonesimulator13.0
|
||||
- SCHEME_IOS="TableKit"
|
||||
- PROJECT_FRAMEWORK="TableKit.xcodeproj"
|
||||
|
||||
matrix:
|
||||
- DESTINATION="OS=10.3.1,name=iPhone 5" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=11.1,name=iPhone 6" 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:
|
||||
- xctool clean build test -project TableKit.xcodeproj -scheme TableKit -sdk iphonesimulator
|
||||
- set -o pipefail
|
||||
- xcodebuild -version
|
||||
- xcodebuild -showsdks
|
||||
- xcodebuild -project "$PROJECT_FRAMEWORK" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO test
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# Change Log
|
||||
|
||||
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)
|
||||
Released on 2018-09-30.
|
||||
- Swift 4.2 support.
|
||||
|
||||
## [2.5.0](https://github.com/maxsokolov/TableKit/releases/tag/2.5.0)
|
||||
Released on 2017-09-24.
|
||||
- Swift 4.0 support.
|
||||
|
||||
## [2.3.0](https://github.com/maxsokolov/TableKit/releases/tag/2.3.0)
|
||||
Released on 2016-11-16.
|
||||
- `shouldUsePrototypeCellHeightCalculation` moved to `TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true)`
|
||||
- Prototype cell height calculation bugfixes
|
||||
|
||||
## [2.1.0](https://github.com/maxsokolov/TableKit/releases/tag/2.1.0)
|
||||
Released on 2016-10-19.
|
||||
- `action` method was deprecated on TableRow. Use `on` instead.
|
||||
- Support multiple actions with same type on row.
|
||||
- You could now build your own cell height calculating strategy. See [TablePrototypeCellHeightCalculator](Sources/TablePrototypeCellHeightCalculator.swift).
|
||||
- Default distance between sections changed to `UITableViewAutomaticDimension`. You can customize it, see [TableSection](Sources/TableSection.swift)
|
||||
|
||||
## [2.0.0](https://github.com/maxsokolov/TableKit/releases/tag/2.0.0)
|
||||
Released on 2016-10-06. Breaking changes in 2.0.0:
|
||||
<br/>The signatures of `TableRow` and `TableRowAction` classes were changed from
|
||||
```swift
|
||||
let action = TableRowAction<String, StringTableViewCell>(.click) { (data) in
|
||||
}
|
||||
|
||||
let row = TableRow<String, StringTableViewCell>(item: "some string", actions: [action])
|
||||
```
|
||||
to
|
||||
```swift
|
||||
let action = TableRowAction<StringTableViewCell>(.click) { (data) in
|
||||
}
|
||||
|
||||
let row = TableRow<StringTableViewCell>(item: "some string", actions: [action])
|
||||
```
|
||||
This is the great improvement that comes from the community. Thanks a lot!
|
||||
|
||||
## [1.3.0](https://github.com/maxsokolov/TableKit/releases/tag/1.3.0)
|
||||
Released on 2016-09-04. Swift 3.0 support.
|
||||
|
||||
## [0.1.0](https://github.com/maxsokolov/TableKit/releases/tag/0.1.0)
|
||||
Released on 2015-11-15. Initial release called Tablet.
|
||||
|
|
@ -1,15 +1,7 @@
|
|||
//
|
||||
// AppDelegate.swift
|
||||
// TabletDemo
|
||||
//
|
||||
// Created by Max Sokolov on 08/11/15.
|
||||
// Copyright © 2015 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,3 @@
|
|||
//
|
||||
// AutolayoutCellsController.swift
|
||||
// TableKitDemo
|
||||
//
|
||||
// Created by Max Sokolov on 18/06/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TableKit
|
||||
|
||||
|
|
@ -13,27 +5,66 @@ class AutolayoutCellsController: UIViewController {
|
|||
|
||||
@IBOutlet weak var tableView: UITableView! {
|
||||
didSet {
|
||||
tableDirector = TableDirector(tableView: tableView)
|
||||
tableDirector.register(ConfigurableTableViewCell.self)
|
||||
tableDirector = TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true)
|
||||
}
|
||||
}
|
||||
var tableDirector: TableDirector!
|
||||
|
||||
func randomString(length: Int) -> String {
|
||||
|
||||
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
let len = UInt32(letters.length)
|
||||
|
||||
var randomString = ""
|
||||
|
||||
for _ in 0 ..< length {
|
||||
let rand = arc4random_uniform(len)
|
||||
var nextChar = letters.character(at: Int(rand))
|
||||
randomString += NSString(characters: &nextChar, length: 1) as String
|
||||
}
|
||||
|
||||
return randomString
|
||||
}
|
||||
|
||||
func randomInt(min: Int, max:Int) -> Int {
|
||||
return min + Int(arc4random_uniform(UInt32(max - min + 1)))
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = "Autolayout cells"
|
||||
|
||||
let section = TableSection()
|
||||
let header = AutolayoutSectionHeaderView.loadFromNib()
|
||||
let section = TableSection(headerView: header, footerView: nil)
|
||||
section.headerHeight = getViewHeight(view: header, width: UIScreen.main.bounds.width)
|
||||
|
||||
var rows = 0
|
||||
while rows <= 10 {
|
||||
while rows <= 20 {
|
||||
rows += 1
|
||||
|
||||
let row = TableRow<Void, AutolayoutTableViewCell>(item: ())
|
||||
let row = TableRow<AutolayoutTableViewCell>(item: randomString(length: randomInt(min: 20, max: 100)))
|
||||
section += row
|
||||
}
|
||||
|
||||
tableDirector += section
|
||||
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Clear", style: .done, target: self, action: #selector(clear))
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func clear() {
|
||||
|
||||
tableDirector.clear()
|
||||
tableDirector.reload()
|
||||
}
|
||||
|
||||
func getViewHeight(view: UIView, width: CGFloat) -> CGFloat {
|
||||
|
||||
view.frame = CGRect(x: 0, y: 0, width: width, height: view.frame.size.height)
|
||||
view.setNeedsLayout()
|
||||
view.layoutIfNeeded()
|
||||
|
||||
return view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,3 @@
|
|||
//
|
||||
// MainController.swift
|
||||
// TabletDemo
|
||||
//
|
||||
// Created by Max Sokolov on 16/04/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TableKit
|
||||
|
||||
|
|
@ -14,7 +6,6 @@ class MainController: UIViewController {
|
|||
@IBOutlet weak var tableView: UITableView! {
|
||||
didSet {
|
||||
tableDirector = TableDirector(tableView: tableView)
|
||||
tableDirector.register(ConfigurableTableViewCell.self)
|
||||
}
|
||||
}
|
||||
var tableDirector: TableDirector!
|
||||
|
|
@ -24,28 +15,30 @@ class MainController: UIViewController {
|
|||
|
||||
title = "TableKit"
|
||||
|
||||
let clickAction = TableRowAction<String, ConfigurableTableViewCell>(.click) { [weak self] (data) in
|
||||
let clickAction = TableRowAction<ConfigurableTableViewCell>(.click) { [weak self] (options) in
|
||||
|
||||
switch data.path.row {
|
||||
switch options.indexPath.row {
|
||||
case 0:
|
||||
self?.performSegueWithIdentifier("autolayoutcells", sender: nil)
|
||||
self?.performSegue(withIdentifier: "autolayoutcells", sender: nil)
|
||||
case 1:
|
||||
self?.performSegueWithIdentifier("rowbuildercells", sender: nil)
|
||||
case 2:
|
||||
self?.performSegueWithIdentifier("nibcells", sender: nil)
|
||||
self?.performSegue(withIdentifier: "nibcells", sender: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let rows: [Row] = [
|
||||
let printClickAction = TableRowAction<ConfigurableTableViewCell>(.click) { (options) in
|
||||
|
||||
print("click", options.indexPath)
|
||||
}
|
||||
|
||||
TableRow<String, ConfigurableTableViewCell>(item: "Autolayout cells", actions: [clickAction]),
|
||||
TableRow<String, ConfigurableTableViewCell>(item: "Row builder cells", actions: [clickAction]),
|
||||
TableRow<String, ConfigurableTableViewCell>(item: "Nib cells", actions: [clickAction])
|
||||
let rows = [
|
||||
|
||||
TableRow<ConfigurableTableViewCell>(item: "Autolayout cells", actions: [clickAction, printClickAction]),
|
||||
TableRow<ConfigurableTableViewCell>(item: "Nib cells", actions: [clickAction, printClickAction])
|
||||
]
|
||||
|
||||
// automatically creates a section, also could be used like tableDirector.append(rows: rows)
|
||||
tableDirector += rows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,3 @@
|
|||
//
|
||||
// NibCellsController.swift
|
||||
// TableKitDemo
|
||||
//
|
||||
// Created by Max Sokolov on 18/06/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TableKit
|
||||
|
||||
|
|
@ -19,16 +11,16 @@ class NibCellsController: UITableViewController {
|
|||
title = "Nib cells"
|
||||
|
||||
tableDirector = TableDirector(tableView: tableView)
|
||||
tableDirector.register(NibTableViewCell.self)
|
||||
|
||||
let numbers = [1000, 2000, 3000, 4000, 5000]
|
||||
|
||||
let shouldHighlightAction = TableRowAction<Int, NibTableViewCell>(.shouldHighlight) { (_) -> Bool in
|
||||
return false
|
||||
|
||||
let rows = numbers.map {
|
||||
TableRow<NibTableViewCell>(item: $0)
|
||||
.on(.shouldHighlight) { (_) -> Bool in
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let rows: [Row] = numbers.map { TableRow<Int, NibTableViewCell>(item: $0, actions: [shouldHighlightAction]) }
|
||||
|
||||
tableDirector.append(rows: rows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
//
|
||||
// RowBuilderCellsController.swift
|
||||
// TableKitDemo
|
||||
//
|
||||
// Created by Max Sokolov on 18/06/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TableKit
|
||||
|
||||
class RowBuilderCellsController: UIViewController {
|
||||
|
||||
@IBOutlet weak var tableView: UITableView! {
|
||||
didSet {
|
||||
tableDirector = TableDirector(tableView: tableView)
|
||||
tableDirector.register(ConfigurableTableViewCell.self)
|
||||
}
|
||||
}
|
||||
var tableDirector: TableDirector!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = "Row builder cells"
|
||||
|
||||
let numbers = ["1", "2", "3", "4", "5"]
|
||||
|
||||
let clickAction = TableRowAction<String, ConfigurableTableViewCell>(.click) { (data) in
|
||||
|
||||
print(data.item)
|
||||
}
|
||||
|
||||
let rowBuilder = TableRowBuilder<String, ConfigurableTableViewCell>(items: numbers, actions: [clickAction])
|
||||
|
||||
let section = TableSection(headerTitle: "Header title", footerTitle: "Footer title")
|
||||
|
||||
section.append(builder: rowBuilder)
|
||||
|
||||
tableDirector.append(section: section)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import UIKit
|
||||
|
||||
final class AutolayoutSectionHeaderView: UIView {
|
||||
|
||||
@IBOutlet weak var testLabel: UILabel!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
testLabel.text = "My super super super super super super super super super super super super super super super super super super super long text!"
|
||||
}
|
||||
|
||||
static func loadFromNib() -> AutolayoutSectionHeaderView {
|
||||
|
||||
let view = Bundle(for: self)
|
||||
.loadNibNamed(
|
||||
String(describing: self),
|
||||
owner: nil,
|
||||
options: nil
|
||||
)?.first
|
||||
|
||||
return view as! AutolayoutSectionHeaderView
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="rrg-Sy-26U" customClass="AutolayoutSectionHeaderView" customModule="TableKitDemo" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="267" height="68"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7al-ia-5Ud">
|
||||
<rect key="frame" x="20" y="24" width="227" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="0.67241452084558406" blue="0.62083349393841014" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="7al-ia-5Ud" secondAttribute="bottom" constant="23" id="0Wx-eb-Gf8"/>
|
||||
<constraint firstAttribute="trailing" secondItem="7al-ia-5Ud" secondAttribute="trailing" constant="20" id="5lG-XV-dd4"/>
|
||||
<constraint firstItem="7al-ia-5Ud" firstAttribute="top" secondItem="rrg-Sy-26U" secondAttribute="top" constant="24" id="VBP-29-bA8"/>
|
||||
<constraint firstItem="7al-ia-5Ud" firstAttribute="leading" secondItem="rrg-Sy-26U" secondAttribute="leading" constant="20" id="cAb-gb-fpr"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<connections>
|
||||
<outlet property="testLabel" destination="7al-ia-5Ud" id="sd9-v6-m3z"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-188.5" y="-277"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
@ -1,31 +1,31 @@
|
|||
//
|
||||
// AutolayoutTableViewCell.swift
|
||||
// TabletDemo
|
||||
//
|
||||
// Created by Max Sokolov on 24/05/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TableKit
|
||||
|
||||
private let LoremIpsumTitle = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"
|
||||
private let LoremIpsumBody = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius adipisci, sed libero. Iste asperiores suscipit, consequatur debitis animi impedit numquam facilis iusto porro labore dolorem, maxime magni incidunt. Delectus, est! Totam at eius excepturi deleniti sed, error repellat itaque omnis maiores tempora ratione dolor velit minus porro aspernatur repudiandae labore quas adipisci esse, nulla tempore voluptatibus cupiditate. Ab provident, atque. Possimus deserunt nisi perferendis, consequuntur odio et aperiam, est, dicta dolor itaque sunt laborum, magni qui optio illum dolore laudantium similique harum. Eveniet quis, libero eligendi delectus repellendus repudiandae ipsum? Vel nam odio dolorem, voluptas sequi minus quo tempore, animi est quia earum maxime. Reiciendis quae repellat, modi non, veniam natus soluta at optio vitae in excepturi minima eveniet dolor."
|
||||
|
||||
class AutolayoutTableViewCell: UITableViewCell, ConfigurableCell {
|
||||
|
||||
typealias T = Void
|
||||
typealias T = String
|
||||
|
||||
@IBOutlet var titleLabel: UILabel!
|
||||
@IBOutlet var subtitleLabel: UILabel!
|
||||
|
||||
func configure(string: T, isPrototype: Bool) {
|
||||
|
||||
static var estimatedHeight: CGFloat? {
|
||||
return 150
|
||||
}
|
||||
|
||||
func configure(with string: T) {
|
||||
|
||||
titleLabel.text = LoremIpsumTitle
|
||||
subtitleLabel.text = LoremIpsumBody
|
||||
subtitleLabel.text = string
|
||||
}
|
||||
|
||||
static func estimatedHeight() -> CGFloat {
|
||||
return 500
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
contentView.layoutIfNeeded()
|
||||
|
||||
titleLabel.preferredMaxLayoutWidth = titleLabel.bounds.size.width
|
||||
subtitleLabel.preferredMaxLayoutWidth = subtitleLabel.bounds.size.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,11 @@
|
|||
//
|
||||
// ConfigurableTableViewCell.swift
|
||||
// TableKitDemo
|
||||
//
|
||||
// Created by Max Sokolov on 18/06/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TableKit
|
||||
|
||||
class ConfigurableTableViewCell: UITableViewCell, ConfigurableCell {
|
||||
|
||||
func configure(text: String, isPrototype: Bool) {
|
||||
func configure(with text: String) {
|
||||
|
||||
accessoryType = .DisclosureIndicator
|
||||
accessoryType = .disclosureIndicator
|
||||
textLabel?.text = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,3 @@
|
|||
//
|
||||
// NibTableViewCell.swift
|
||||
// TableKitDemo
|
||||
//
|
||||
// Created by Max Sokolov on 18/06/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TableKit
|
||||
|
||||
|
|
@ -13,11 +5,11 @@ class NibTableViewCell: UITableViewCell, ConfigurableCell {
|
|||
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
|
||||
func configure(number: Int, isPrototype: Bool) {
|
||||
titleLabel.text = "\(number)"
|
||||
}
|
||||
|
||||
static func defaultHeight() -> CGFloat? {
|
||||
static var defaultHeight: CGFloat? {
|
||||
return 100
|
||||
}
|
||||
}
|
||||
|
||||
func configure(with number: Int) {
|
||||
titleLabel.text = "\(number)"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10116" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10117" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
|
|
@ -11,19 +11,21 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4JI-V9-Bra" id="Bal-V1-EZ2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qBe-iP-Q1p">
|
||||
<rect key="frame" x="8" y="11" width="45" height="21"/>
|
||||
<rect key="frame" x="8" y="11" width="304" height="21"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="qBe-iP-Q1p" firstAttribute="centerY" secondItem="Bal-V1-EZ2" secondAttribute="centerY" id="8gn-1I-szV"/>
|
||||
<constraint firstItem="qBe-iP-Q1p" firstAttribute="leading" secondItem="Bal-V1-EZ2" secondAttribute="leading" constant="8" id="xMK-TB-Bis"/>
|
||||
<constraint firstItem="qBe-iP-Q1p" firstAttribute="top" secondItem="Bal-V1-EZ2" secondAttribute="top" constant="11" id="B84-GR-YoJ"/>
|
||||
<constraint firstAttribute="bottom" secondItem="qBe-iP-Q1p" secondAttribute="bottom" constant="12" id="Cmh-Tj-2Pw"/>
|
||||
<constraint firstAttribute="trailing" secondItem="qBe-iP-Q1p" secondAttribute="trailing" constant="8" id="ZJP-9s-4ZM"/>
|
||||
<constraint firstItem="qBe-iP-Q1p" firstAttribute="leading" secondItem="Bal-V1-EZ2" secondAttribute="leading" constant="8" id="wAW-KV-dCJ"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="dOU-ON-YYD">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="dOU-ON-YYD">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
<rect key="frame" x="0.0" y="92" width="600" height="141"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="IBY-tW-SgU" id="UNZ-nz-200">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="140"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="141"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="751" verticalHuggingPriority="751" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" placeholderIntrinsicWidth="172" placeholderIntrinsicHeight="21" text="Title" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YnK-4H-0SS">
|
||||
|
|
@ -139,7 +139,6 @@
|
|||
<connections>
|
||||
<outlet property="tableView" destination="lVS-GW-taC" id="C28-ml-ARU"/>
|
||||
<segue destination="grv-aL-Qbb" kind="show" identifier="autolayoutcells" id="YrM-qz-6Kd"/>
|
||||
<segue destination="sSs-TX-Ch0" kind="show" identifier="rowbuildercells" id="bRQ-rh-ksD"/>
|
||||
<segue destination="apk-bL-6NO" kind="show" identifier="nibcells" id="gXD-XZ-oBm"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
|
|
@ -147,40 +146,6 @@
|
|||
</objects>
|
||||
<point key="canvasLocation" x="1046" y="287"/>
|
||||
</scene>
|
||||
<!--Row Builder Cells Controller-->
|
||||
<scene sceneID="Jd0-of-RF9">
|
||||
<objects>
|
||||
<viewController id="sSs-TX-Ch0" customClass="RowBuilderCellsController" customModule="TableKitDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="s9s-Gu-3M6"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Tux-up-dnH"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="CB5-10-J2D">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="uLr-Ff-utK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Tux-up-dnH" firstAttribute="top" secondItem="uLr-Ff-utK" secondAttribute="bottom" id="0jr-6S-PFB"/>
|
||||
<constraint firstItem="uLr-Ff-utK" firstAttribute="top" secondItem="CB5-10-J2D" secondAttribute="top" id="KYu-01-hlH"/>
|
||||
<constraint firstAttribute="trailing" secondItem="uLr-Ff-utK" secondAttribute="trailing" id="TKR-R1-lBY"/>
|
||||
<constraint firstItem="uLr-Ff-utK" firstAttribute="leading" secondItem="CB5-10-J2D" secondAttribute="leading" id="aG6-R3-QUr"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="ogA-Id-clb"/>
|
||||
<connections>
|
||||
<outlet property="tableView" destination="uLr-Ff-utK" id="olP-LV-VmT"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Gim-O8-I3d" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1762" y="648"/>
|
||||
</scene>
|
||||
<!--Nib Cells Controller-->
|
||||
<scene sceneID="DHa-3S-OkR">
|
||||
<objects>
|
||||
|
|
@ -197,7 +162,7 @@
|
|||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="oWG-DK-A9C" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1762" y="1366"/>
|
||||
<point key="canvasLocation" x="1762" y="671"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5079ADE31FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5079ADE21FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift */; };
|
||||
5079ADE61FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5079ADE51FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib */; };
|
||||
DA08A0531CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA08A0521CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift */; };
|
||||
DA55465D1D1569CC00AA83EE /* AutolayoutCellsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA55465C1D1569CC00AA83EE /* AutolayoutCellsController.swift */; };
|
||||
DA5546601D156A4F00AA83EE /* ConfigurableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA55465F1D156A4F00AA83EE /* ConfigurableTableViewCell.swift */; };
|
||||
DA5546621D1573D300AA83EE /* RowBuilderCellsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5546611D1573D300AA83EE /* RowBuilderCellsController.swift */; };
|
||||
DA5546641D15762000AA83EE /* NibTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5546631D15762000AA83EE /* NibTableViewCell.swift */; };
|
||||
DA5546661D15765900AA83EE /* NibTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA5546651D15765900AA83EE /* NibTableViewCell.xib */; };
|
||||
DA5546681D15771D00AA83EE /* NibCellsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5546671D15771D00AA83EE /* NibCellsController.swift */; };
|
||||
|
|
@ -62,10 +63,11 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5079ADE21FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutolayoutSectionHeaderView.swift; sourceTree = "<group>"; };
|
||||
5079ADE51FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AutolayoutSectionHeaderView.xib; sourceTree = "<group>"; };
|
||||
DA08A0521CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutolayoutTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DA55465C1D1569CC00AA83EE /* AutolayoutCellsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutolayoutCellsController.swift; sourceTree = "<group>"; };
|
||||
DA55465F1D156A4F00AA83EE /* ConfigurableTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DA5546611D1573D300AA83EE /* RowBuilderCellsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowBuilderCellsController.swift; sourceTree = "<group>"; };
|
||||
DA5546631D15762000AA83EE /* NibTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DA5546651D15765900AA83EE /* NibTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NibTableViewCell.xib; sourceTree = "<group>"; };
|
||||
DA5546671D15771D00AA83EE /* NibCellsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibCellsController.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -184,7 +186,6 @@
|
|||
children = (
|
||||
DACB71751CC2D63D00432BD3 /* MainController.swift */,
|
||||
DA55465C1D1569CC00AA83EE /* AutolayoutCellsController.swift */,
|
||||
DA5546611D1573D300AA83EE /* RowBuilderCellsController.swift */,
|
||||
DA5546671D15771D00AA83EE /* NibCellsController.swift */,
|
||||
);
|
||||
path = Controllers;
|
||||
|
|
@ -197,6 +198,8 @@
|
|||
DA08A0521CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift */,
|
||||
DA5546631D15762000AA83EE /* NibTableViewCell.swift */,
|
||||
DA5546651D15765900AA83EE /* NibTableViewCell.xib */,
|
||||
5079ADE21FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift */,
|
||||
5079ADE51FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -230,12 +233,13 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0720;
|
||||
LastUpgradeCheck = 0700;
|
||||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = Tablet;
|
||||
TargetAttributes = {
|
||||
DAB7EB261BEF787300D2AD5E = {
|
||||
CreatedOnToolsVersion = 7.0.1;
|
||||
DevelopmentTeam = Z48R734SJX;
|
||||
LastSwiftMigration = 1000;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -244,6 +248,7 @@
|
|||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
|
|
@ -286,6 +291,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DAC2D5CF1C9D30A7009E9C19 /* Main.storyboard in Resources */,
|
||||
5079ADE61FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib in Resources */,
|
||||
DA5546661D15765900AA83EE /* NibTableViewCell.xib in Resources */,
|
||||
DAC2D5D01C9D30A7009E9C19 /* LaunchScreen.storyboard in Resources */,
|
||||
DAC2D69C1C9E75E3009E9C19 /* Assets.xcassets in Resources */,
|
||||
|
|
@ -300,10 +306,10 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DACB71761CC2D63D00432BD3 /* MainController.swift in Sources */,
|
||||
DA5546621D1573D300AA83EE /* RowBuilderCellsController.swift in Sources */,
|
||||
DA55465D1D1569CC00AA83EE /* AutolayoutCellsController.swift in Sources */,
|
||||
DA5546681D15771D00AA83EE /* NibCellsController.swift in Sources */,
|
||||
DAC2D5CA1C9D303E009E9C19 /* AppDelegate.swift in Sources */,
|
||||
5079ADE31FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift in Sources */,
|
||||
DA5546641D15762000AA83EE /* NibTableViewCell.swift in Sources */,
|
||||
DA5546601D156A4F00AA83EE /* ConfigurableTableViewCell.swift in Sources */,
|
||||
DA08A0531CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift in Sources */,
|
||||
|
|
@ -329,13 +335,23 @@
|
|||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
|
|
@ -362,6 +378,7 @@
|
|||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -373,13 +390,23 @@
|
|||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
|
|
@ -398,6 +425,8 @@
|
|||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
|
|
@ -405,32 +434,34 @@
|
|||
DAB7EB3A1BEF787300D2AD5E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
||||
INFOPLIST_FILE = Resources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
|
||||
PRODUCT_NAME = TableKitDemo;
|
||||
PROVISIONING_PROFILE = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DAB7EB3B1BEF787300D2AD5E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
||||
INFOPLIST_FILE = Resources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
|
||||
PRODUCT_NAME = TableKitDemo;
|
||||
PROVISIONING_PROFILE = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -1,5 +1,19 @@
|
|||
// swift-tools-version:5.7
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "TableKit"
|
||||
)
|
||||
name: "TableKit",
|
||||
|
||||
products: [
|
||||
.library(
|
||||
name: "TableKit",
|
||||
targets: ["TableKit"]),
|
||||
],
|
||||
|
||||
targets: [
|
||||
.target(
|
||||
name: "TableKit",
|
||||
path: "Sources")
|
||||
]
|
||||
)
|
||||
|
|
|
|||
203
README.md
203
README.md
|
|
@ -1,10 +1,10 @@
|
|||
#TableKit
|
||||
# TableKit
|
||||
|
||||
<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://developer.apple.com/swift"><img src="https://img.shields.io/badge/Swift_2.2-compatible-4BC51D.svg?style=flat" alt="Swift 2.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://cocoapods.org/pods/tablekit"><img src="https://img.shields.io/badge/pod-0.8.2-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" />
|
||||
<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>
|
||||
|
|
@ -12,11 +12,12 @@
|
|||
TableKit is a super lightweight yet powerful generic library that allows you to build complex table views in a declarative type-safe manner.
|
||||
It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` methods behind the scene, so your code will be look clean, easy to read and nice to maintain.
|
||||
|
||||
## Features
|
||||
# Features
|
||||
|
||||
- [x] Type-safe generic cells
|
||||
- [x] Functional programming style friendly
|
||||
- [x] The easiest way to map your models or view models to cells
|
||||
- [x] Automatic cell registration*
|
||||
- [x] Correctly handles autolayout cells with multiline labels
|
||||
- [x] Chainable cell actions (select/deselect etc.)
|
||||
- [x] Support cells created from code, xib, or storyboard
|
||||
|
|
@ -25,17 +26,19 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho
|
|||
- [x] No need to subclass
|
||||
- [x] Extensibility
|
||||
|
||||
## Getting Started
|
||||
# Getting Started
|
||||
|
||||
An [example app](Demo) is included demonstrating TableKit's functionality.
|
||||
|
||||
#### Basic usage
|
||||
## Basic usage
|
||||
|
||||
Create your rows:
|
||||
```swift
|
||||
let row1 = TableRow<String, StringTableViewCell>(item: "1")
|
||||
let row2 = TableRow<Int, IntTableViewCell>(item: 2)
|
||||
let row3 = TableRow<Float, FloatTableViewCell>(item: 3.0)
|
||||
import TableKit
|
||||
|
||||
let row1 = TableRow<StringTableViewCell>(item: "1")
|
||||
let row2 = TableRow<IntTableViewCell>(item: 2)
|
||||
let row3 = TableRow<UserTableViewCell>(item: User(name: "John Doe", rating: 5))
|
||||
```
|
||||
Put rows into section:
|
||||
```swift
|
||||
|
|
@ -44,140 +47,198 @@ let section = TableSection(rows: [row1, row2, row3])
|
|||
And setup your table:
|
||||
```swift
|
||||
let tableDirector = TableDirector(tableView: tableView)
|
||||
tableDirector.register(StringTableViewCell.self, IntTableViewCell.self, FloatTableViewCell.self)
|
||||
tableDirector += section
|
||||
```
|
||||
Done. Your table is ready. You may want to look at your cell. It has to conform to `ConfigurableCell` protocol:
|
||||
Done. Your table is ready. Your cells have to conform to `ConfigurableCell` protocol:
|
||||
```swift
|
||||
class StringTableViewCell: UITableViewCell, ConfigurableCell {
|
||||
|
||||
typealias T = String
|
||||
func configure(with string: String) {
|
||||
|
||||
textLabel?.text = string
|
||||
}
|
||||
}
|
||||
|
||||
func configure(string: T, isPrototype: Bool) {
|
||||
titleLabel.text = string
|
||||
}
|
||||
class UserTableViewCell: UITableViewCell, ConfigurableCell {
|
||||
|
||||
static func estimatedHeight() -> CGFloat {
|
||||
return 44
|
||||
static var estimatedHeight: CGFloat? {
|
||||
return 100
|
||||
}
|
||||
|
||||
// is not required to be implemented
|
||||
// by default reuse id is equal to cell's class name
|
||||
static var reuseIdentifier: String {
|
||||
return "my id"
|
||||
}
|
||||
|
||||
func configure(with user: User) {
|
||||
|
||||
textLabel?.text = user.name
|
||||
detailTextLabel?.text = "Rating: \(user.rating)"
|
||||
}
|
||||
}
|
||||
```
|
||||
You could have as many rows and sections as you need.
|
||||
|
||||
#### Row actions
|
||||
## Row actions
|
||||
|
||||
It nice to have some actions that related to your cells:
|
||||
```swift
|
||||
let action = TableRowAction<String, StringTableViewCell>(.click) { (data) in
|
||||
let action = TableRowAction<StringTableViewCell>(.click) { (options) in
|
||||
|
||||
// you could access any useful information that relates to the action
|
||||
// you could access any useful information that relates to the action
|
||||
|
||||
// data.cell - StringTableViewCell?
|
||||
// data.item - String
|
||||
// data.path - NSIndexPath
|
||||
// options.cell - StringTableViewCell?
|
||||
// options.item - String
|
||||
// options.indexPath - IndexPath
|
||||
// options.userInfo - [AnyHashable: Any]?
|
||||
}
|
||||
|
||||
let row = TableRow<String, StringTableViewCell>(item: "some", actions: [action])
|
||||
let row = TableRow<StringTableViewCell>(item: "some", actions: [action])
|
||||
```
|
||||
Or, using nice chaining approach:
|
||||
```swift
|
||||
let row = TableRow<String, StringTableViewCell>(item: "some")
|
||||
.action(TableRowAction(.click) { (data) in
|
||||
let row = TableRow<StringTableViewCell>(item: "some")
|
||||
.on(.click) { (options) in
|
||||
|
||||
})
|
||||
.action(TableRowAction(.shouldHighlight) { (data) -> Bool in
|
||||
return false
|
||||
})
|
||||
}
|
||||
.on(.shouldHighlight) { (options) -> Bool in
|
||||
return false
|
||||
}
|
||||
```
|
||||
You could find all available actions [here](Sources/TableRowAction.swift).
|
||||
|
||||
#### Batch rows
|
||||
You could have a situation when you need a lot of cells with the same type. In that case it's better to use `TableRowBuilder`:
|
||||
```swift
|
||||
let builder = TableRowBuilder<String, StringTableViewCell> {
|
||||
## Custom row actions
|
||||
|
||||
// do some additional setup here
|
||||
$0.items = ["1", "2", "3"]
|
||||
$0.actions = [action]
|
||||
You are able to define your own actions:
|
||||
```swift
|
||||
struct MyActions {
|
||||
|
||||
static let ButtonClicked = "ButtonClicked"
|
||||
}
|
||||
|
||||
section.append(builder: builder)
|
||||
class MyTableViewCell: UITableViewCell, ConfigurableCell {
|
||||
|
||||
@IBAction func myButtonClicked(sender: UIButton) {
|
||||
|
||||
TableCellAction(key: MyActions.ButtonClicked, sender: self).invoke()
|
||||
}
|
||||
}
|
||||
```
|
||||
Or if you don't need an additional setup for your data, just use standart init:
|
||||
And handle them accordingly:
|
||||
```swift
|
||||
let builder = TableRowBuilder<String, StringTableViewCell>(items: ["1", "2", "3"], actions: [actions])
|
||||
let myAction = TableRowAction<MyTableViewCell>(.custom(MyActions.ButtonClicked)) { (options) in
|
||||
|
||||
section.append(builder: builder)
|
||||
}
|
||||
```
|
||||
## Multiple actions with same type
|
||||
|
||||
It's also possible to use multiple actions with same type:
|
||||
```swift
|
||||
let click1 = TableRowAction<StringTableViewCell>(.click) { (options) in }
|
||||
click1.id = "click1" // optional
|
||||
|
||||
let click2 = TableRowAction<StringTableViewCell>(.click) { (options) in }
|
||||
click2.id = "click2" // optional
|
||||
|
||||
let row = TableRow<StringTableViewCell>(item: "some", actions: [click1, click2])
|
||||
```
|
||||
Could be useful in case if you want to separate your logic somehow. Actions will be invoked in order which they were attached.
|
||||
> If you define multiple actions with same type which also return a value, only last return value will be used for table view.
|
||||
|
||||
You could also remove any action by id:
|
||||
```swift
|
||||
row.removeAction(forActionId: "action_id")
|
||||
```
|
||||
|
||||
## Advanced
|
||||
# Advanced
|
||||
|
||||
#### Cell height calculating strategy
|
||||
## Cell height calculating strategy
|
||||
By default TableKit relies on <a href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html" target="_blank">self-sizing cells</a>. In that case you have to provide an estimated height for your cells:
|
||||
```swift
|
||||
class StringTableViewCell: UITableViewCell, ConfigurableCell {
|
||||
|
||||
// ...
|
||||
// ...
|
||||
|
||||
static func estimatedHeight() -> CGFloat {
|
||||
return 44
|
||||
static var estimatedHeight: CGFloat? {
|
||||
return 255
|
||||
}
|
||||
}
|
||||
```
|
||||
It's enough for most cases. But you may be not happy with this. So you could use a prototype cell to calculate cell's heights. To enable this feature simply use this property:
|
||||
It's enough for most cases. But you may be not happy with this. So you could use a prototype cell to calculate cells heights. To enable this feature simply use this property:
|
||||
```swift
|
||||
tableDirector.shouldUsePrototypeCellHeightCalculation = true
|
||||
let tableDirector = TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true)
|
||||
```
|
||||
It does all dirty work with prototypes for you [behind the scene](Sources/HeightStrategy.swift), so you don't have to worry about anything except of your cell configuration:
|
||||
It does all dirty work with prototypes for you [behind the scene](Sources/TablePrototypeCellHeightCalculator.swift), so you don't have to worry about anything except of your cell configuration:
|
||||
```swift
|
||||
class ImageTableViewCell: UITableViewCell, ConfigurableCell {
|
||||
|
||||
func configure(url: NSURL, isPrototype: Bool) {
|
||||
func configure(with url: NSURL) {
|
||||
|
||||
if !isPrototype {
|
||||
loadImageAsync(url: url, imageView: imageView)
|
||||
}
|
||||
}
|
||||
loadImageAsync(url: url, imageView: imageView)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
contentView.layoutIfNeeded()
|
||||
multilineLabel.preferredMaxLayoutWidth = multilineLabel.bounds.size.width
|
||||
contentView.layoutIfNeeded()
|
||||
multilineLabel.preferredMaxLayoutWidth = multilineLabel.bounds.size.width
|
||||
}
|
||||
}
|
||||
```
|
||||
First of all you have to set `preferredMaxLayoutWidth` for all your multiline labels. And check if a configuring cell is a prototype cell. If it is, you don't have to do any additional work that not actually affect cell's height. For example you don't have to load remote image for a prototype cell.
|
||||
You have to additionally set `preferredMaxLayoutWidth` for all your multiline labels.
|
||||
|
||||
#### Functional programming
|
||||
## Functional programming
|
||||
It's never been so easy to deal with table views.
|
||||
```swift
|
||||
let users = /* some users array */
|
||||
|
||||
let rows: [Row] = users.filter({ $0.state == .active }).map({ TableRow<String, UserTableViewCell>(item: $0.username) })
|
||||
let click = TableRowAction<UserTableViewCell>(.click) {
|
||||
|
||||
}
|
||||
|
||||
let rows = users.filter({ $0.state == .active }).map({ TableRow<UserTableViewCell>(item: $0.name, actions: [click]) })
|
||||
|
||||
tableDirector += rows
|
||||
```
|
||||
Done, your table is ready. It's just awesome!
|
||||
Done, your table is ready.
|
||||
## Automatic cell registration
|
||||
|
||||
## Installation
|
||||
TableKit can register your cells in a table view automatically. In case if your reusable cell id matches cell's xib name:
|
||||
|
||||
#### CocoaPods
|
||||
```ruby
|
||||
MyTableViewCell.swift
|
||||
MyTableViewCell.xib
|
||||
|
||||
```
|
||||
You can also turn off this behaviour:
|
||||
```swift
|
||||
let tableDirector = TableDirector(tableView: tableView, shouldUseAutomaticCellRegistration: false)
|
||||
```
|
||||
and register your cell manually.
|
||||
|
||||
# Installation
|
||||
|
||||
## CocoaPods
|
||||
To integrate TableKit into your Xcode project using CocoaPods, specify it in your `Podfile`:
|
||||
|
||||
```ruby
|
||||
pod 'TableKit'
|
||||
```
|
||||
#### Carthage
|
||||
## Carthage
|
||||
Add the line `github "maxsokolov/tablekit"` to your `Cartfile`.
|
||||
#### Manual
|
||||
## Manual
|
||||
Clone the repo and drag files from `Sources` folder into your Xcode project.
|
||||
|
||||
## Requirements
|
||||
# Requirements
|
||||
|
||||
- iOS 8.0+
|
||||
- Xcode 7.0+
|
||||
- iOS 8.0
|
||||
- Xcode 9.0
|
||||
|
||||
## License
|
||||
# Changelog
|
||||
|
||||
TableKit is available under the MIT license. See LICENSE for details.
|
||||
Keep an eye on [changes](CHANGELOG.md).
|
||||
|
||||
# License
|
||||
|
||||
TableKit is available under the MIT license. See LICENSE for details.
|
||||
|
|
|
|||
|
|
@ -20,39 +20,38 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
public protocol ReusableCell {
|
||||
|
||||
static func reusableIdentifier() -> String
|
||||
static func nib() -> UINib?
|
||||
}
|
||||
public protocol ConfigurableCell {
|
||||
|
||||
public protocol ConfigurableCell: ReusableCell {
|
||||
associatedtype CellData
|
||||
|
||||
associatedtype T
|
||||
static var reuseIdentifier: String { get }
|
||||
|
||||
static func estimatedHeight() -> CGFloat
|
||||
static func defaultHeight() -> CGFloat?
|
||||
func configure(_: T, isPrototype: Bool)
|
||||
}
|
||||
static var estimatedHeight: CGFloat? { get }
|
||||
|
||||
static var defaultHeight: CGFloat? { get }
|
||||
|
||||
static var layoutType: LayoutType { get }
|
||||
|
||||
func configure(with _: CellData)
|
||||
|
||||
public extension ReusableCell where Self: UITableViewCell {
|
||||
|
||||
static func reusableIdentifier() -> String {
|
||||
return String(self)
|
||||
}
|
||||
|
||||
static func nib() -> UINib? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public extension ConfigurableCell where Self: UITableViewCell {
|
||||
|
||||
static func estimatedHeight() -> CGFloat {
|
||||
return UITableViewAutomaticDimension
|
||||
|
||||
static var reuseIdentifier: String {
|
||||
return String(describing: self)
|
||||
}
|
||||
|
||||
static func defaultHeight() -> CGFloat? {
|
||||
|
||||
static var estimatedHeight: CGFloat? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static var defaultHeight: CGFloat? {
|
||||
return nil
|
||||
}
|
||||
|
||||
static var layoutType: LayoutType {
|
||||
return .auto
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
import UIKit
|
||||
|
||||
public extension TimeInterval {
|
||||
|
||||
static let defaultExpandableAnimationDuration: TimeInterval = 0.3
|
||||
|
||||
}
|
||||
|
||||
public protocol Expandable {
|
||||
|
||||
associatedtype ViewModelType: ExpandableCellViewModel
|
||||
|
||||
var viewModel: ViewModelType? { get }
|
||||
|
||||
func configure(state: ExpandableState)
|
||||
|
||||
}
|
||||
|
||||
extension Expandable where Self: UITableViewCell & ConfigurableCell {
|
||||
|
||||
public func initState() {
|
||||
guard let viewModel = viewModel else {
|
||||
return
|
||||
}
|
||||
|
||||
changeState(expandableState: viewModel.expandableState)
|
||||
}
|
||||
|
||||
private func changeState(expandableState: ExpandableState) {
|
||||
// layout to get right frames, frame of bottom subview can be used to get expanded height
|
||||
setNeedsLayout()
|
||||
layoutIfNeeded()
|
||||
|
||||
// apply changes
|
||||
configure(state: expandableState)
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
public func toggleState(animated: Bool = true,
|
||||
animationDuration: TimeInterval = .defaultExpandableAnimationDuration) {
|
||||
|
||||
guard let viewModel = viewModel,
|
||||
let stateIndex = viewModel.availableStates.firstIndex(where: { $0 == viewModel.expandableState }) else {
|
||||
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
|
||||
|
||||
if animated {
|
||||
UIView.animate(withDuration: animationDuration,
|
||||
animations: { [weak self] in
|
||||
self?.applyChanges(expandableState: state)
|
||||
}, completion: { _ in
|
||||
viewModel.expandableState = state
|
||||
})
|
||||
} else {
|
||||
applyChanges(expandableState: state)
|
||||
viewModel.expandableState = state
|
||||
}
|
||||
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
|
||||
tableView.setContentOffset(contentOffset, animated: false)
|
||||
}
|
||||
|
||||
public func applyChanges(expandableState: ExpandableState) {
|
||||
changeState(expandableState: expandableState)
|
||||
|
||||
if let indexPath = indexPath,
|
||||
let tableDirector = (tableView?.delegate as? TableDirector),
|
||||
let cellHeightCalculator = tableDirector.rowHeightCalculator as? ExpandableCellHeightCalculator {
|
||||
cellHeightCalculator.updateCached(height: expandableState.height ?? height(layoutType: Self.layoutType), for: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import UIKit
|
||||
|
||||
public final class ExpandableCellHeightCalculator: RowHeightCalculator {
|
||||
|
||||
private weak var tableView: UITableView?
|
||||
|
||||
private var prototypes = [String: UITableViewCell]()
|
||||
|
||||
private var cachedHeights = [IndexPath: CGFloat]()
|
||||
|
||||
public init(tableView: UITableView?) {
|
||||
self.tableView = tableView
|
||||
}
|
||||
|
||||
public func updateCached(height: CGFloat, for indexPath: IndexPath) {
|
||||
cachedHeights[indexPath] = height
|
||||
}
|
||||
|
||||
public func height(forRow row: Row, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
guard let tableView = tableView else {
|
||||
return 0
|
||||
}
|
||||
|
||||
if let height = cachedHeights[indexPath] {
|
||||
return height
|
||||
}
|
||||
|
||||
var prototypeCell = prototypes[row.reuseIdentifier]
|
||||
if prototypeCell == nil {
|
||||
prototypeCell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier)
|
||||
prototypes[row.reuseIdentifier] = prototypeCell
|
||||
}
|
||||
|
||||
guard let cell = prototypeCell else {
|
||||
return 0
|
||||
}
|
||||
|
||||
row.configure(cell)
|
||||
cell.layoutIfNeeded()
|
||||
|
||||
let height = cell.height(layoutType: row.layoutType)
|
||||
cachedHeights[indexPath] = height
|
||||
return height
|
||||
}
|
||||
|
||||
public func estimatedHeight(forRow row: Row, at indexPath: IndexPath) -> CGFloat {
|
||||
return height(forRow: row, at: indexPath)
|
||||
}
|
||||
|
||||
public func invalidate() {
|
||||
cachedHeights.removeAll()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
public protocol ExpandableCellViewModel: AnyObject {
|
||||
|
||||
var expandableState: ExpandableState { get set }
|
||||
|
||||
var availableStates: [ExpandableState] { get }
|
||||
|
||||
}
|
||||
|
||||
public extension ExpandableCellViewModel {
|
||||
|
||||
var availableStates: [ExpandableState] {
|
||||
return [.collapsed, .expanded]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
public enum LayoutType {
|
||||
|
||||
case manual
|
||||
|
||||
case auto
|
||||
|
||||
}
|
||||
|
|
@ -43,4 +43,4 @@ public func +=(left: TableSection, right: Row) {
|
|||
|
||||
public func +=(left: TableSection, right: [Row]) {
|
||||
left.append(rows: right)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,15 +20,11 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
struct TableKitNotifications {
|
||||
static let CellAction = "TableKitNotificationsCellAction"
|
||||
}
|
||||
|
||||
/**
|
||||
A custom action that you can trigger from your cell.
|
||||
You can eacily catch actions using a chaining manner with your row builder.
|
||||
You can easily catch actions using a chaining manner with your row.
|
||||
*/
|
||||
public class TableCellAction {
|
||||
open class TableCellAction {
|
||||
|
||||
/// The cell that triggers an action.
|
||||
public let cell: UITableViewCell
|
||||
|
|
@ -37,16 +33,16 @@ public class TableCellAction {
|
|||
public let key: String
|
||||
|
||||
/// The custom user info.
|
||||
public let userInfo: [NSObject: AnyObject]?
|
||||
public let userInfo: [AnyHashable: Any]?
|
||||
|
||||
public init(key: String, sender: UITableViewCell, userInfo: [NSObject: AnyObject]? = nil) {
|
||||
public init(key: String, sender: UITableViewCell, userInfo: [AnyHashable: Any]? = nil) {
|
||||
|
||||
self.key = key
|
||||
self.cell = sender
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
public func invoke() {
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(TableKitNotifications.CellAction, object: self, userInfo: userInfo)
|
||||
open func invoke() {
|
||||
NotificationCenter.default.post(name: Notification.Name(rawValue: TableKitNotifications.CellAction), object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,39 +20,39 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
public protocol RowBuilder {
|
||||
class TableCellRegisterer {
|
||||
|
||||
func rows() -> [Row]?
|
||||
}
|
||||
|
||||
public class TableRowBuilder<ItemType, CellType: ConfigurableCell where CellType.T == ItemType, CellType: UITableViewCell>: RowBuilder {
|
||||
|
||||
public var items: [ItemType]?
|
||||
public var actions: [TableRowAction<ItemType, CellType>]?
|
||||
private var registeredIds = Set<String>()
|
||||
private weak var tableView: UITableView?
|
||||
|
||||
public init(handler: (TableRowBuilder) -> ()) {
|
||||
handler(self)
|
||||
init(tableView: UITableView?) {
|
||||
self.tableView = tableView
|
||||
}
|
||||
|
||||
public init(items: [ItemType], actions: [TableRowAction<ItemType, CellType>]? = nil) {
|
||||
|
||||
self.items = items
|
||||
self.actions = actions
|
||||
}
|
||||
|
||||
// MARK: - RowBuilder -
|
||||
|
||||
public func rows() -> [Row]? {
|
||||
return items?.map { TableRow<ItemType, CellType>(item: $0, actions: actions) }
|
||||
}
|
||||
}
|
||||
|
||||
public extension TableSection {
|
||||
|
||||
public func append(builder builder: RowBuilder) {
|
||||
|
||||
if let rows = builder.rows() {
|
||||
append(rows: rows)
|
||||
func register(cellType: AnyClass, forCellReuseIdentifier reuseIdentifier: String) {
|
||||
|
||||
if registeredIds.contains(reuseIdentifier) {
|
||||
return
|
||||
}
|
||||
|
||||
// check if cell is already registered, probably cell has been registered by storyboard
|
||||
if tableView?.dequeueReusableCell(withIdentifier: reuseIdentifier) != nil {
|
||||
|
||||
registeredIds.insert(reuseIdentifier)
|
||||
return
|
||||
}
|
||||
|
||||
let bundle = Bundle(for: cellType)
|
||||
|
||||
// we hope that cell's xib file has name that equals to cell's class name
|
||||
// in that case we could register nib
|
||||
if let _ = bundle.path(forResource: reuseIdentifier, ofType: "nib") {
|
||||
tableView?.register(UINib(nibName: reuseIdentifier, bundle: bundle), forCellReuseIdentifier: reuseIdentifier)
|
||||
// otherwise, register cell class
|
||||
} else {
|
||||
tableView?.register(cellType, forCellReuseIdentifier: reuseIdentifier)
|
||||
}
|
||||
|
||||
registeredIds.insert(reuseIdentifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,227 +23,453 @@ import UIKit
|
|||
/**
|
||||
Responsible for table view's datasource and delegate.
|
||||
*/
|
||||
public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
public private(set) weak var tableView: UITableView?
|
||||
public private(set) var sections = [TableSection]()
|
||||
open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
open private(set) weak var tableView: UITableView?
|
||||
open fileprivate(set) var sections = [TableSection]()
|
||||
|
||||
private weak var scrollDelegate: UIScrollViewDelegate?
|
||||
private var heightStrategy: CellHeightCalculatable?
|
||||
|
||||
public var shouldUsePrototypeCellHeightCalculation: Bool = false {
|
||||
private var cellRegisterer: TableCellRegisterer?
|
||||
public private(set) var rowHeightCalculator: RowHeightCalculator?
|
||||
private var sectionsIndexTitlesIndexes: [Int]?
|
||||
|
||||
@available(*, deprecated, message: "Produced incorrect behaviour")
|
||||
open var shouldUsePrototypeCellHeightCalculation: Bool = false {
|
||||
didSet {
|
||||
if shouldUsePrototypeCellHeightCalculation {
|
||||
heightStrategy = PrototypeHeightStrategy(tableView: tableView)
|
||||
rowHeightCalculator = TablePrototypeCellHeightCalculator(tableView: tableView)
|
||||
} else {
|
||||
rowHeightCalculator = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init(tableView: UITableView, scrollDelegate: UIScrollViewDelegate? = nil) {
|
||||
|
||||
open var isEmpty: Bool {
|
||||
return sections.isEmpty
|
||||
}
|
||||
|
||||
public init(
|
||||
tableView: UITableView,
|
||||
scrollDelegate: UIScrollViewDelegate? = nil,
|
||||
shouldUseAutomaticCellRegistration: Bool = true,
|
||||
cellHeightCalculator: RowHeightCalculator?)
|
||||
{
|
||||
super.init()
|
||||
|
||||
|
||||
if shouldUseAutomaticCellRegistration {
|
||||
self.cellRegisterer = TableCellRegisterer(tableView: tableView)
|
||||
}
|
||||
|
||||
self.rowHeightCalculator = cellHeightCalculator
|
||||
self.scrollDelegate = scrollDelegate
|
||||
self.tableView = tableView
|
||||
self.tableView?.delegate = self
|
||||
self.tableView?.dataSource = self
|
||||
|
||||
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(didReceiveAction), name: TableKitNotifications.CellAction, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveAction), name: NSNotification.Name(rawValue: TableKitNotifications.CellAction), object: nil)
|
||||
}
|
||||
|
||||
public convenience init(
|
||||
tableView: UITableView,
|
||||
scrollDelegate: UIScrollViewDelegate? = nil,
|
||||
shouldUseAutomaticCellRegistration: Bool = true,
|
||||
shouldUsePrototypeCellHeightCalculation: Bool = false)
|
||||
{
|
||||
let heightCalculator: TablePrototypeCellHeightCalculator? = shouldUsePrototypeCellHeightCalculation
|
||||
? TablePrototypeCellHeightCalculator(tableView: tableView)
|
||||
: nil
|
||||
|
||||
self.init(
|
||||
tableView: tableView,
|
||||
scrollDelegate: scrollDelegate,
|
||||
shouldUseAutomaticCellRegistration: shouldUseAutomaticCellRegistration,
|
||||
cellHeightCalculator: heightCalculator
|
||||
)
|
||||
}
|
||||
|
||||
deinit {
|
||||
NSNotificationCenter.defaultCenter().removeObserver(self)
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
public func reload() {
|
||||
open func reload() {
|
||||
tableView?.reloadData()
|
||||
}
|
||||
|
||||
public func register<T: UITableViewCell where T: ReusableCell>(cells: T.Type...) {
|
||||
|
||||
for cell in cells {
|
||||
|
||||
if let nib = cell.nib() {
|
||||
|
||||
tableView?.registerNib(nib, forCellReuseIdentifier: cell.reusableIdentifier())
|
||||
return
|
||||
|
||||
} else {
|
||||
|
||||
let resource = cell.reusableIdentifier()
|
||||
let bundle = NSBundle(forClass: cell)
|
||||
|
||||
if let _ = bundle.pathForResource(resource, ofType: "nib") {
|
||||
tableView?.registerNib(UINib(nibName: resource, bundle: bundle), forCellReuseIdentifier: cell.reusableIdentifier())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tableView?.registerClass(cell, forCellReuseIdentifier: cell.reusableIdentifier())
|
||||
// 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
|
||||
|
||||
public func invoke(action action: TableRowActionType, cell: UITableViewCell?, indexPath: NSIndexPath) -> Any? {
|
||||
return sections[indexPath.section].items[indexPath.row].invoke(action, cell: cell, path: indexPath)
|
||||
}
|
||||
|
||||
public override func respondsToSelector(selector: Selector) -> Bool {
|
||||
return super.respondsToSelector(selector) || scrollDelegate?.respondsToSelector(selector) == true
|
||||
}
|
||||
|
||||
public override func forwardingTargetForSelector(selector: Selector) -> AnyObject? {
|
||||
return scrollDelegate?.respondsToSelector(selector) == true ? scrollDelegate : super.forwardingTargetForSelector(selector)
|
||||
@discardableResult
|
||||
open func invoke(
|
||||
action: TableRowActionType,
|
||||
cell: UITableViewCell?, indexPath: IndexPath,
|
||||
userInfo: [AnyHashable: Any]? = nil) -> Any?
|
||||
{
|
||||
guard let row = row(at: indexPath) else { return nil }
|
||||
return row.invoke(
|
||||
action: action,
|
||||
cell: cell,
|
||||
path: indexPath,
|
||||
userInfo: userInfo
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Internal -
|
||||
|
||||
func hasAction(action: TableRowActionType, atIndexPath indexPath: NSIndexPath) -> Bool {
|
||||
return sections[indexPath.section].items[indexPath.row].hasAction(action)
|
||||
open override func responds(to selector: Selector) -> Bool {
|
||||
return super.responds(to: selector) || scrollDelegate?.responds(to: selector) == true
|
||||
}
|
||||
|
||||
func didReceiveAction(notification: NSNotification) {
|
||||
|
||||
guard let action = notification.object as? TableCellAction, indexPath = tableView?.indexPathForCell(action.cell) else { return }
|
||||
invoke(action: .custom(action.key), cell: action.cell, indexPath: indexPath)
|
||||
open override func forwardingTarget(for selector: Selector) -> Any? {
|
||||
return scrollDelegate?.responds(to: selector) == true
|
||||
? scrollDelegate
|
||||
: super.forwardingTarget(for: selector)
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
func hasAction(_ action: TableRowActionType, atIndexPath indexPath: IndexPath) -> Bool {
|
||||
guard let row = row(at: indexPath) else { return false }
|
||||
return row.has(action: action)
|
||||
}
|
||||
|
||||
@objc
|
||||
func didReceiveAction(_ notification: Notification) {
|
||||
|
||||
guard let action = notification.object as? TableCellAction, let indexPath = tableView?.indexPath(for: action.cell) else { return }
|
||||
invoke(action: .custom(action.key), cell: action.cell, indexPath: indexPath, userInfo: notification.userInfo)
|
||||
}
|
||||
|
||||
// MARK: - Height
|
||||
|
||||
public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
|
||||
open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
let row = sections[indexPath.section].items[indexPath.row]
|
||||
return heightStrategy?.estimatedHeight(row, path: indexPath) ?? row.estimatedHeight
|
||||
let row = sections[indexPath.section].rows[indexPath.row]
|
||||
|
||||
if rowHeightCalculator != nil {
|
||||
cellRegisterer?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier)
|
||||
}
|
||||
|
||||
return row.defaultHeight
|
||||
?? row.estimatedHeight
|
||||
?? rowHeightCalculator?.estimatedHeight(forRow: row, at: indexPath)
|
||||
?? UITableView.automaticDimension
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
|
||||
open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
let row = sections[indexPath.section].rows[indexPath.row]
|
||||
|
||||
if rowHeightCalculator != nil {
|
||||
cellRegisterer?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier)
|
||||
}
|
||||
|
||||
let row = sections[indexPath.section].items[indexPath.row]
|
||||
return heightStrategy?.height(row, path: indexPath) ?? row.defaultHeight
|
||||
let rowHeight = invoke(action: .height, cell: nil, indexPath: indexPath) as? CGFloat
|
||||
|
||||
return rowHeight
|
||||
?? row.defaultHeight
|
||||
?? rowHeightCalculator?.height(forRow: row, at: indexPath)
|
||||
?? UITableView.automaticDimension
|
||||
}
|
||||
|
||||
// MARK: UITableViewDataSource - configuration
|
||||
|
||||
public func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
open func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return sections.count
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
guard section < sections.count else { return 0 }
|
||||
|
||||
return sections[section].numberOfRows
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
|
||||
let row = sections[indexPath.section].items[indexPath.row]
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier(row.reusableIdentifier, forIndexPath: indexPath)
|
||||
open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let row = sections[indexPath.section].rows[indexPath.row]
|
||||
|
||||
cellRegisterer?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier)
|
||||
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier, for: indexPath)
|
||||
|
||||
if cell.frame.size.width != tableView.frame.size.width {
|
||||
cell.frame = CGRectMake(0, 0, tableView.frame.size.width, cell.frame.size.height)
|
||||
cell.frame = CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: cell.frame.size.height)
|
||||
cell.layoutIfNeeded()
|
||||
}
|
||||
|
||||
row.configure(cell, isPrototype: false)
|
||||
|
||||
|
||||
row.configure(cell)
|
||||
invoke(action: .configure, cell: cell, indexPath: indexPath)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: UITableViewDataSource - section setup
|
||||
|
||||
public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
guard section < sections.count else { return nil }
|
||||
|
||||
return sections[section].headerTitle
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
guard section < sections.count else { return nil }
|
||||
|
||||
return sections[section].footerTitle
|
||||
}
|
||||
|
||||
// MARK: UITableViewDelegate - section setup
|
||||
|
||||
public func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
guard section < sections.count else { return nil }
|
||||
|
||||
return sections[section].headerView
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
guard section < sections.count else { return nil }
|
||||
|
||||
return sections[section].footerView
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
return sections[section].headerView?.frame.size.height ?? UITableViewAutomaticDimension
|
||||
open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
guard section < sections.count else { return 0 }
|
||||
|
||||
let section = sections[section]
|
||||
return section.headerHeight ?? section.headerView?.frame.size.height ?? UITableView.automaticDimension
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
return sections[section].footerView?.frame.size.height ?? UITableViewAutomaticDimension
|
||||
open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
guard section < sections.count else { return 0 }
|
||||
|
||||
let section = sections[section]
|
||||
return section.footerHeight
|
||||
?? section.footerView?.frame.size.height
|
||||
?? UITableView.automaticDimension
|
||||
}
|
||||
|
||||
// MARK: UITableViewDataSource - Index
|
||||
public func sectionIndexTitles(for tableView: UITableView) -> [String]? {
|
||||
|
||||
var indexTitles = [String]()
|
||||
var indexTitlesIndexes = [Int]()
|
||||
sections.enumerated().forEach { index, section in
|
||||
|
||||
if let title = section.indexTitle {
|
||||
indexTitles.append(title)
|
||||
indexTitlesIndexes.append(index)
|
||||
}
|
||||
}
|
||||
if !indexTitles.isEmpty {
|
||||
|
||||
sectionsIndexTitlesIndexes = indexTitlesIndexes
|
||||
return indexTitles
|
||||
}
|
||||
sectionsIndexTitlesIndexes = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
public func tableView(
|
||||
_ tableView: UITableView,
|
||||
sectionForSectionIndexTitle title: String,
|
||||
at index: Int) -> Int
|
||||
{
|
||||
return sectionsIndexTitlesIndexes?[index] ?? 0
|
||||
}
|
||||
|
||||
// MARK: UITableViewDelegate - actions
|
||||
|
||||
public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
let cell = tableView.cellForRowAtIndexPath(indexPath)
|
||||
open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let cell = tableView.cellForRow(at: indexPath)
|
||||
|
||||
if invoke(action: .click, cell: cell, indexPath: indexPath) != nil {
|
||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
} else {
|
||||
invoke(action: .select, cell: cell, indexPath: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
invoke(action: .deselect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath)
|
||||
open func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
|
||||
invoke(action: .deselect, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath)
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
|
||||
open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
invoke(action: .willDisplay, cell: cell, indexPath: indexPath)
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
|
||||
return invoke(action: .shouldHighlight, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? true
|
||||
|
||||
public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
invoke(action: .didEndDisplaying, cell: cell, indexPath: indexPath)
|
||||
}
|
||||
|
||||
public func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
|
||||
|
||||
|
||||
open func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
||||
return invoke(action: .shouldHighlight, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? true
|
||||
}
|
||||
|
||||
open func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
if hasAction(.willSelect, atIndexPath: indexPath) {
|
||||
return invoke(action: .willSelect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? NSIndexPath
|
||||
return invoke(action: .willSelect, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? IndexPath
|
||||
}
|
||||
|
||||
return indexPath
|
||||
}
|
||||
|
||||
// MARK: - Sections manipulation -
|
||||
|
||||
public func append(section section: TableSection) -> Self {
|
||||
|
||||
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
|
||||
open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
return sections[indexPath.section].rows[indexPath.row].isEditingAllowed(forIndexPath: indexPath)
|
||||
}
|
||||
|
||||
open func tableView(_ tableView: UITableView,
|
||||
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 {
|
||||
if invoke(action: .canDelete, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? false {
|
||||
return UITableViewCell.EditingStyle.delete
|
||||
}
|
||||
|
||||
return UITableViewCell.EditingStyle.none
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
|
||||
return invoke(action: .canMoveTo, cell: tableView.cellForRow(at: sourceIndexPath), indexPath: sourceIndexPath, userInfo: [TableKitUserInfoKeys.CellCanMoveProposedIndexPath: proposedDestinationIndexPath]) as? IndexPath ?? proposedDestinationIndexPath
|
||||
}
|
||||
|
||||
open func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
if editingStyle == .delete {
|
||||
invoke(action: .clickDelete, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
open func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
|
||||
return invoke(action: .canMove, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? false
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
open func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||
let cell = tableView.cellForRow(at: indexPath)
|
||||
invoke(action: .accessoryButtonTap, cell: cell, indexPath: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sections manipulation
|
||||
extension TableDirector {
|
||||
|
||||
@discardableResult
|
||||
public func append(section: TableSection) -> Self {
|
||||
|
||||
append(sections: [section])
|
||||
return self
|
||||
}
|
||||
|
||||
public func append(sections sections: [TableSection]) -> Self {
|
||||
|
||||
sections.forEach { $0.tableDirector = self }
|
||||
self.sections.appendContentsOf(sections)
|
||||
@discardableResult
|
||||
public func append(sections: [TableSection]) -> Self {
|
||||
|
||||
self.sections.append(contentsOf: sections)
|
||||
return self
|
||||
}
|
||||
|
||||
public func append(rows rows: [Row]) -> Self {
|
||||
|
||||
@discardableResult
|
||||
public func append(rows: [Row]) -> Self {
|
||||
|
||||
append(section: TableSection(rows: rows))
|
||||
return self
|
||||
}
|
||||
|
||||
public func insert(section section: TableSection, atIndex index: Int) -> Self {
|
||||
|
||||
sections.insert(section, atIndex: index)
|
||||
return self
|
||||
}
|
||||
|
||||
public func delete(index index: Int) -> Self {
|
||||
@discardableResult
|
||||
public func insert(section: TableSection, atIndex index: Int) -> Self {
|
||||
|
||||
sections.removeAtIndex(index)
|
||||
sections.insert(section, at: index)
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func replaceSection(at index: Int, with section: TableSection) -> Self {
|
||||
|
||||
if index < sections.count {
|
||||
sections[index] = section
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func delete(sectionAt index: Int) -> Self {
|
||||
|
||||
sections.remove(at: index)
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func remove(sectionAt index: Int) -> Self {
|
||||
return delete(sectionAt: index)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func clear() -> Self {
|
||||
|
||||
rowHeightCalculator?.invalidate()
|
||||
sections.removeAll()
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - deprecated methods
|
||||
@available(*, deprecated, message: "Use 'delete(sectionAt:)' method instead")
|
||||
@discardableResult
|
||||
public func delete(index: Int) -> Self {
|
||||
|
||||
sections.remove(at: index)
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
//
|
||||
// Copyright (c) 2015 Max Sokolov https://twitter.com/max_sokolov
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
struct TableKitNotifications {
|
||||
static let CellAction = "TableKitNotificationsCellAction"
|
||||
}
|
||||
|
||||
public struct TableKitUserInfoKeys {
|
||||
public static let CellMoveDestinationIndexPath = "TableKitCellMoveDestinationIndexPath"
|
||||
public static let CellCanMoveProposedIndexPath = "CellCanMoveProposedIndexPath"
|
||||
public static let ContextMenuInvokePoint = "ContextMenuInvokePoint"
|
||||
}
|
||||
|
||||
public protocol RowConfigurable {
|
||||
|
||||
func configure(_ cell: UITableViewCell)
|
||||
|
||||
}
|
||||
|
||||
public protocol RowActionable {
|
||||
var leadingContextualActions: [UIContextualAction] { get }
|
||||
var trailingContextualActions: [UIContextualAction] { get }
|
||||
var performsFirstActionWithFullSwipe: Bool { get }
|
||||
|
||||
func isEditingAllowed(forIndexPath indexPath: IndexPath) -> Bool
|
||||
|
||||
func invoke(
|
||||
action: TableRowActionType,
|
||||
cell: UITableViewCell?,
|
||||
path: IndexPath,
|
||||
userInfo: [AnyHashable: Any]?) -> Any?
|
||||
|
||||
func has(action: TableRowActionType) -> Bool
|
||||
}
|
||||
|
||||
public protocol RowHashable {
|
||||
|
||||
var hashValue: Int { get }
|
||||
}
|
||||
|
||||
public protocol Row: RowConfigurable, RowActionable, RowHashable {
|
||||
|
||||
var reuseIdentifier: String { get }
|
||||
var cellType: AnyClass { get }
|
||||
|
||||
var layoutType: LayoutType { get }
|
||||
var estimatedHeight: CGFloat? { get }
|
||||
var defaultHeight: CGFloat? { get }
|
||||
}
|
||||
|
||||
public enum TableRowActionType {
|
||||
|
||||
case click
|
||||
case clickDelete
|
||||
case select
|
||||
case deselect
|
||||
case willSelect
|
||||
case willDeselect
|
||||
case willDisplay
|
||||
case didEndDisplaying
|
||||
case shouldHighlight
|
||||
case shouldBeginMultipleSelection
|
||||
case didBeginMultipleSelection
|
||||
case height
|
||||
case canEdit
|
||||
case configure
|
||||
case canDelete
|
||||
case canMove
|
||||
case canMoveTo
|
||||
case move
|
||||
case showContextMenu
|
||||
case accessoryButtonTap
|
||||
case custom(String)
|
||||
|
||||
var key: String {
|
||||
|
||||
switch (self) {
|
||||
case .custom(let key):
|
||||
return key
|
||||
default:
|
||||
return "_\(self)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol RowHeightCalculator {
|
||||
|
||||
func height(forRow row: Row, at indexPath: IndexPath) -> CGFloat
|
||||
func estimatedHeight(forRow row: Row, at indexPath: IndexPath) -> CGFloat
|
||||
|
||||
func invalidate()
|
||||
}
|
||||
|
|
@ -20,26 +20,18 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
public protocol CellHeightCalculatable {
|
||||
open class TablePrototypeCellHeightCalculator: RowHeightCalculator {
|
||||
|
||||
func height(row: Row, path: NSIndexPath) -> CGFloat
|
||||
func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat
|
||||
|
||||
func invalidate()
|
||||
}
|
||||
|
||||
public class PrototypeHeightStrategy: CellHeightCalculatable {
|
||||
|
||||
private weak var tableView: UITableView?
|
||||
private(set) weak var tableView: UITableView?
|
||||
private var prototypes = [String: UITableViewCell]()
|
||||
private var cachedHeights = [Int: CGFloat]()
|
||||
private var separatorHeight = 1 / UIScreen.mainScreen().scale
|
||||
private var separatorHeight = 1 / UIScreen.main.scale
|
||||
|
||||
init(tableView: UITableView?) {
|
||||
public init(tableView: UITableView?) {
|
||||
self.tableView = tableView
|
||||
}
|
||||
|
||||
public func height(row: Row, path: NSIndexPath) -> CGFloat {
|
||||
open func height(forRow row: Row, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
guard let tableView = tableView else { return 0 }
|
||||
|
||||
|
|
@ -49,27 +41,47 @@ public class PrototypeHeightStrategy: CellHeightCalculatable {
|
|||
return height
|
||||
}
|
||||
|
||||
guard let cell = tableView.dequeueReusableCellWithIdentifier(row.reusableIdentifier) else { return 0 }
|
||||
var prototypeCell = prototypes[row.reuseIdentifier]
|
||||
if prototypeCell == nil {
|
||||
|
||||
cell.bounds = CGRectMake(0, 0, tableView.bounds.size.width, cell.bounds.height)
|
||||
|
||||
row.configure(cell, isPrototype: true)
|
||||
prototypeCell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier)
|
||||
prototypes[row.reuseIdentifier] = prototypeCell
|
||||
}
|
||||
|
||||
guard let cell = prototypeCell else { return 0 }
|
||||
|
||||
cell.prepareForReuse()
|
||||
row.configure(cell)
|
||||
|
||||
cell.bounds = CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: cell.bounds.height)
|
||||
cell.setNeedsLayout()
|
||||
cell.layoutIfNeeded()
|
||||
|
||||
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (tableView.separatorStyle != .None ? separatorHeight : 0)
|
||||
let height = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + (tableView.separatorStyle != .none ? separatorHeight : 0)
|
||||
|
||||
cachedHeights[hash] = height
|
||||
|
||||
return height
|
||||
}
|
||||
|
||||
public func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat {
|
||||
return UITableViewAutomaticDimension
|
||||
open func estimatedHeight(forRow row: Row, at indexPath: IndexPath) -> CGFloat {
|
||||
|
||||
guard let tableView = tableView else { return 0 }
|
||||
|
||||
let hash = row.hashValue ^ Int(tableView.bounds.size.width).hashValue
|
||||
|
||||
if let height = cachedHeights[hash] {
|
||||
return height
|
||||
}
|
||||
|
||||
if let estimatedHeight = row.estimatedHeight , estimatedHeight > 0 {
|
||||
return estimatedHeight
|
||||
}
|
||||
|
||||
return UITableView.automaticDimension
|
||||
}
|
||||
|
||||
public func invalidate() {
|
||||
open func invalidate() {
|
||||
cachedHeights.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,83 +20,137 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
public protocol RowConfigurable {
|
||||
open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableViewCell {
|
||||
|
||||
func configure(cell: UITableViewCell, isPrototype: Bool)
|
||||
}
|
||||
public let item: CellType.CellData
|
||||
private lazy var actions = [String: [TableRowAction<CellType>]]()
|
||||
|
||||
public protocol RowActionable {
|
||||
open var leadingContextualActions: [UIContextualAction] {
|
||||
[]
|
||||
}
|
||||
|
||||
func invoke(action: TableRowActionType, cell: UITableViewCell?, path: NSIndexPath) -> Any?
|
||||
func hasAction(action: TableRowActionType) -> Bool
|
||||
}
|
||||
open var trailingContextualActions: [UIContextualAction] {
|
||||
[]
|
||||
}
|
||||
|
||||
public protocol RowHashable {
|
||||
open var performsFirstActionWithFullSwipe: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
var hashValue: Int { get }
|
||||
}
|
||||
|
||||
public protocol Row: RowConfigurable, RowActionable, RowHashable {
|
||||
|
||||
var reusableIdentifier: String { get }
|
||||
var estimatedHeight: CGFloat { get }
|
||||
var defaultHeight: CGFloat { get }
|
||||
}
|
||||
|
||||
public class TableRow<ItemType, CellType: ConfigurableCell where CellType.T == ItemType, CellType: UITableViewCell>: Row {
|
||||
|
||||
public let item: ItemType
|
||||
private lazy var actions = [String: TableRowAction<ItemType, CellType>]()
|
||||
|
||||
public var hashValue: Int {
|
||||
open var hashValue: Int {
|
||||
return ObjectIdentifier(self).hashValue
|
||||
}
|
||||
|
||||
public var reusableIdentifier: String {
|
||||
return CellType.reusableIdentifier()
|
||||
|
||||
open var reuseIdentifier: String {
|
||||
return CellType.reuseIdentifier
|
||||
}
|
||||
|
||||
public var estimatedHeight: CGFloat {
|
||||
return CellType.estimatedHeight()
|
||||
}
|
||||
|
||||
public var defaultHeight: CGFloat {
|
||||
return CellType.defaultHeight() ?? UITableViewAutomaticDimension
|
||||
open var estimatedHeight: CGFloat? {
|
||||
return CellType.estimatedHeight
|
||||
}
|
||||
|
||||
public init(item: ItemType, actions: [TableRowAction<ItemType, CellType>]? = nil) {
|
||||
open var defaultHeight: CGFloat? {
|
||||
return CellType.defaultHeight
|
||||
}
|
||||
|
||||
open var layoutType: LayoutType {
|
||||
return CellType.layoutType
|
||||
}
|
||||
|
||||
open var cellType: AnyClass {
|
||||
return CellType.self
|
||||
}
|
||||
|
||||
public init(item: CellType.CellData,
|
||||
actions: [TableRowAction<CellType>]? = nil) {
|
||||
|
||||
self.item = item
|
||||
actions?.forEach { self.actions[$0.type.key] = $0 }
|
||||
|
||||
actions?.forEach { on($0) }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - RowConfigurable -
|
||||
|
||||
public func configure(cell: UITableViewCell, isPrototype: Bool) {
|
||||
(cell as? CellType)?.configure(item, isPrototype: isPrototype)
|
||||
open func configure(_ cell: UITableViewCell) {
|
||||
|
||||
(cell as? CellType)?.configure(with: item)
|
||||
}
|
||||
|
||||
// MARK: - RowActionable -
|
||||
|
||||
open func invoke(action: TableRowActionType, cell: UITableViewCell?, path: IndexPath, userInfo: [AnyHashable: Any]? = nil) -> Any? {
|
||||
|
||||
return actions[action.key]?.compactMap({ $0.invokeActionOn(cell: cell, item: item, path: path, userInfo: userInfo) }).last
|
||||
}
|
||||
|
||||
// MARK: - RowActionable -
|
||||
|
||||
public func invoke(action: TableRowActionType, cell: UITableViewCell?, path: NSIndexPath) -> Any? {
|
||||
return actions[action.key]?.invoke(item: item, cell: cell, path: path)
|
||||
}
|
||||
|
||||
public func hasAction(action: TableRowActionType) -> Bool {
|
||||
open func has(action: TableRowActionType) -> Bool {
|
||||
|
||||
return actions[action.key] != nil
|
||||
}
|
||||
|
||||
open func isEditingAllowed(forIndexPath indexPath: IndexPath) -> Bool {
|
||||
|
||||
if actions[TableRowActionType.canEdit.key] != nil {
|
||||
return invoke(action: .canEdit, cell: nil, path: indexPath) as? Bool ?? false
|
||||
}
|
||||
|
||||
return !leadingContextualActions.isEmpty
|
||||
|| !trailingContextualActions.isEmpty
|
||||
|| actions[TableRowActionType.clickDelete.key] != nil
|
||||
}
|
||||
|
||||
// MARK: - actions -
|
||||
|
||||
public func action(action: TableRowAction<ItemType, CellType>) -> Self {
|
||||
@discardableResult
|
||||
open func on(_ action: TableRowAction<CellType>) -> Self {
|
||||
|
||||
actions[action.type.key] = action
|
||||
if actions[action.type.key] == nil {
|
||||
actions[action.type.key] = [TableRowAction<CellType>]()
|
||||
}
|
||||
actions[action.type.key]?.append(action)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func on<T>(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> T) -> Self {
|
||||
|
||||
return on(TableRowAction<CellType>(type, handler: handler))
|
||||
}
|
||||
|
||||
public func action<T>(type: TableRowActionType, handler: (data: TableRowActionData<ItemType, CellType>) -> T) -> Self {
|
||||
@discardableResult
|
||||
open func on(_ key: String, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> ()) -> Self {
|
||||
|
||||
actions[type.key] = TableRowAction(type, handler: handler)
|
||||
return self
|
||||
return on(TableRowAction<CellType>(.custom(key), handler: handler))
|
||||
}
|
||||
}
|
||||
|
||||
open func removeAllActions() {
|
||||
|
||||
actions.removeAll()
|
||||
}
|
||||
|
||||
open func removeAction(forActionId actionId: String) {
|
||||
|
||||
for (key, value) in actions {
|
||||
if let actionIndex = value.firstIndex(where: { $0.id == actionId }) {
|
||||
actions[key]?.remove(at: actionIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - deprecated actions -
|
||||
|
||||
@available(*, deprecated, message: "Use 'on' method instead")
|
||||
@discardableResult
|
||||
open func action(_ action: TableRowAction<CellType>) -> Self {
|
||||
|
||||
return on(action)
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use 'on' method instead")
|
||||
@discardableResult
|
||||
open func action<T>(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> T) -> Self {
|
||||
|
||||
return on(TableRowAction<CellType>(type, handler: handler))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,62 +20,64 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
public enum TableRowActionType {
|
||||
|
||||
case click
|
||||
case select
|
||||
case deselect
|
||||
case willSelect
|
||||
case willDisplay
|
||||
case shouldHighlight
|
||||
case height
|
||||
case custom(String)
|
||||
open class TableRowActionOptions<CellType: ConfigurableCell> where CellType: UITableViewCell {
|
||||
|
||||
var key: String {
|
||||
|
||||
switch (self) {
|
||||
case .custom(let key):
|
||||
return key
|
||||
default:
|
||||
return "_\(self)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TableRowActionData<ItemType, CellType: ConfigurableCell where CellType.T == ItemType, CellType: UITableViewCell> {
|
||||
|
||||
public let item: ItemType
|
||||
public let item: CellType.CellData
|
||||
public let cell: CellType?
|
||||
public let path: NSIndexPath
|
||||
public let userInfo: [NSObject: AnyObject]?
|
||||
public let indexPath: IndexPath
|
||||
public let userInfo: [AnyHashable: Any]?
|
||||
|
||||
init(item: ItemType, cell: CellType?, path: NSIndexPath, userInfo: [NSObject: AnyObject]?) {
|
||||
init(item: CellType.CellData, cell: CellType?, path: IndexPath, userInfo: [AnyHashable: Any]?) {
|
||||
|
||||
self.item = item
|
||||
self.cell = cell
|
||||
self.path = path
|
||||
self.indexPath = path
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
}
|
||||
|
||||
public class TableRowAction<ItemType, CellType: ConfigurableCell where CellType.T == ItemType, CellType: UITableViewCell> {
|
||||
private enum TableRowActionHandler<CellType: ConfigurableCell> where CellType: UITableViewCell {
|
||||
|
||||
case voidAction((TableRowActionOptions<CellType>) -> Void)
|
||||
case action((TableRowActionOptions<CellType>) -> Any?)
|
||||
|
||||
func invoke(withOptions options: TableRowActionOptions<CellType>) -> Any? {
|
||||
|
||||
switch self {
|
||||
case .voidAction(let handler):
|
||||
return handler(options)
|
||||
case .action(let handler):
|
||||
return handler(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class TableRowAction<CellType: ConfigurableCell> where CellType: UITableViewCell {
|
||||
|
||||
open var id: String?
|
||||
public let type: TableRowActionType
|
||||
private let handler: ((data: TableRowActionData<ItemType, CellType>) -> Any?)
|
||||
|
||||
public init(_ type: TableRowActionType, handler: (data: TableRowActionData<ItemType, CellType>) -> Void) {
|
||||
private let handler: TableRowActionHandler<CellType>
|
||||
|
||||
public init(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> Void) {
|
||||
|
||||
self.type = type
|
||||
self.handler = handler
|
||||
self.handler = .voidAction(handler)
|
||||
}
|
||||
|
||||
public init<T>(_ type: TableRowActionType, handler: (data: TableRowActionData<ItemType, CellType>) -> T) {
|
||||
public init(_ key: String, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> Void) {
|
||||
|
||||
self.type = .custom(key)
|
||||
self.handler = .voidAction(handler)
|
||||
}
|
||||
|
||||
public init<T>(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> T) {
|
||||
|
||||
self.type = type
|
||||
self.handler = handler
|
||||
self.handler = .action(handler)
|
||||
}
|
||||
|
||||
func invoke(item item: ItemType, cell: UITableViewCell?, path: NSIndexPath) -> Any? {
|
||||
return handler(data: TableRowActionData(item: item, cell: cell as? CellType, path: path, userInfo: nil))
|
||||
public func invokeActionOn(cell: UITableViewCell?, item: CellType.CellData, path: IndexPath, userInfo: [AnyHashable: Any]?) -> Any? {
|
||||
|
||||
return handler.invoke(withOptions: TableRowActionOptions(item: item, cell: cell as? CellType, path: path, userInfo: userInfo))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,36 +20,42 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
public class TableSection {
|
||||
open class TableSection {
|
||||
|
||||
weak var tableDirector: TableDirector?
|
||||
|
||||
public private(set) var items = [Row]()
|
||||
|
||||
public var headerTitle: String?
|
||||
public var footerTitle: String?
|
||||
|
||||
public private(set) var headerView: UIView?
|
||||
public private(set) var footerView: UIView?
|
||||
|
||||
public var numberOfRows: Int {
|
||||
return items.count
|
||||
open private(set) var rows = [Row]()
|
||||
|
||||
open var headerTitle: String?
|
||||
open var footerTitle: String?
|
||||
open var indexTitle: String?
|
||||
|
||||
open var headerView: UIView?
|
||||
open var footerView: UIView?
|
||||
|
||||
open var headerHeight: CGFloat? = nil
|
||||
open var footerHeight: CGFloat? = nil
|
||||
|
||||
open var numberOfRows: Int {
|
||||
return rows.count
|
||||
}
|
||||
|
||||
open var isEmpty: Bool {
|
||||
return rows.isEmpty
|
||||
}
|
||||
|
||||
public init(rows: [Row]? = nil) {
|
||||
|
||||
|
||||
if let initialRows = rows {
|
||||
items.appendContentsOf(initialRows)
|
||||
self.rows.append(contentsOf: initialRows)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public convenience init(headerTitle: String?, footerTitle: String?, rows: [Row]? = nil) {
|
||||
self.init(rows: rows)
|
||||
|
||||
self.headerTitle = headerTitle
|
||||
self.footerTitle = footerTitle
|
||||
}
|
||||
|
||||
|
||||
public convenience init(headerView: UIView?, footerView: UIView?, rows: [Row]? = nil) {
|
||||
self.init(rows: rows)
|
||||
|
||||
|
|
@ -58,24 +64,47 @@ public class TableSection {
|
|||
}
|
||||
|
||||
// MARK: - Public -
|
||||
|
||||
public func clear() {
|
||||
items.removeAll()
|
||||
|
||||
open func clear() {
|
||||
rows.removeAll()
|
||||
}
|
||||
|
||||
public func append(row row: Row) {
|
||||
open func append(row: Row) {
|
||||
append(rows: [row])
|
||||
}
|
||||
|
||||
public func append(rows rows: [Row]) {
|
||||
items.appendContentsOf(rows)
|
||||
open func append(rows: [Row]) {
|
||||
self.rows.append(contentsOf: rows)
|
||||
}
|
||||
|
||||
open func insert(row: Row, at index: Int) {
|
||||
rows.insert(row, at: index)
|
||||
}
|
||||
|
||||
open func insert(rows: [Row], at index: Int) {
|
||||
self.rows.insert(contentsOf: rows, at: index)
|
||||
}
|
||||
|
||||
public func insert(row row: Row, atIndex index: Int) {
|
||||
items.insert(row, atIndex: index)
|
||||
open func replace(rowAt index: Int, with row: Row) {
|
||||
rows[index] = row
|
||||
}
|
||||
|
||||
public func delete(index: Int) {
|
||||
items.removeAtIndex(index)
|
||||
|
||||
open func swap(from: Int, to: Int) {
|
||||
rows.swapAt(from, to)
|
||||
}
|
||||
}
|
||||
|
||||
open func delete(rowAt index: Int) {
|
||||
rows.remove(at: index)
|
||||
}
|
||||
|
||||
open func remove(rowAt index: Int) {
|
||||
rows.remove(at: index)
|
||||
}
|
||||
|
||||
// MARK: - deprecated methods -
|
||||
|
||||
@available(*, deprecated, message: "Use 'delete(rowAt:)' method instead")
|
||||
open func delete(index: Int) {
|
||||
rows.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import UIKit
|
||||
|
||||
extension UITableViewCell {
|
||||
|
||||
var tableView: UITableView? {
|
||||
var view = superview
|
||||
|
||||
while view != nil && !(view is UITableView) {
|
||||
view = view?.superview
|
||||
}
|
||||
|
||||
return view as? UITableView
|
||||
}
|
||||
|
||||
var indexPath: IndexPath? {
|
||||
guard let indexPath = tableView?.indexPath(for: self) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return indexPath
|
||||
}
|
||||
|
||||
public func height(layoutType: LayoutType) -> CGFloat {
|
||||
switch layoutType {
|
||||
case .auto:
|
||||
return contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
|
||||
case .manual:
|
||||
return contentView.subviews.map { $0.frame.maxY }.max() ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,16 +2,16 @@ Pod::Spec.new do |s|
|
|||
s.name = 'TableKit'
|
||||
s.module_name = 'TableKit'
|
||||
|
||||
s.version = '0.8.2'
|
||||
s.version = '2.12'
|
||||
|
||||
s.homepage = 'https://github.com/maxsokolov/TableKit'
|
||||
s.summary = 'Type-safe declarative table views. Swift 2.2 is required.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/TableKit'
|
||||
s.summary = 'Type-safe declarative table views with Swift.'
|
||||
|
||||
s.author = { 'Max Sokolov' => 'i@maxsokolov.net' }
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.platforms = { :ios => '8.0' }
|
||||
s.ios.deployment_target = '8.0'
|
||||
s.platforms = { :ios => '12.0' }
|
||||
s.ios.deployment_target = '12.0'
|
||||
|
||||
s.source_files = 'Sources/*.swift'
|
||||
s.source = { :git => 'https://github.com/maxsokolov/TableKit.git', :tag => s.version }
|
||||
end
|
||||
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/TableKit.git', :tag => s.version }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,14 +7,21 @@
|
|||
objects = {
|
||||
|
||||
/* 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 */; };
|
||||
3201E78621BE9E25001DF9E7 /* UITableViewCell+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */; };
|
||||
3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78721BE9EB2001DF9E7 /* Expandable.swift */; };
|
||||
3201E78A21BE9ED4001DF9E7 /* ExpandableCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */; };
|
||||
32BDFE9F21C167F400D0BBB4 /* LayoutType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */; };
|
||||
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */; };
|
||||
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E858571DB153F500A9AA55 /* TableKit.swift */; };
|
||||
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */; };
|
||||
DA9EA7B01D0EC2C90021F650 /* HeightStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A71D0EC2C90021F650 /* HeightStrategy.swift */; };
|
||||
DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */; };
|
||||
DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A81D0EC2C90021F650 /* Operators.swift */; };
|
||||
DA9EA7B21D0EC2C90021F650 /* TableCellAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */; };
|
||||
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */; };
|
||||
DA9EA7B41D0EC2C90021F650 /* TableRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */; };
|
||||
DA9EA7B51D0EC2C90021F650 /* TableRowAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */; };
|
||||
DA9EA7B61D0EC2C90021F650 /* TableRowBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AD1D0EC2C90021F650 /* TableRowBuilder.swift */; };
|
||||
DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */; };
|
||||
DA9EA7C91D0EC45F0021F650 /* TableKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9EA7561D0B679A0021F650 /* TableKit.framework */; };
|
||||
DA9EA7CF1D0EC4930021F650 /* TableKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7BE1D0EC41D0021F650 /* TableKitTests.swift */; };
|
||||
|
|
@ -31,15 +38,22 @@
|
|||
/* End PBXContainerItemProxy 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>"; };
|
||||
3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+Extensions.swift"; sourceTree = "<group>"; };
|
||||
3201E78721BE9EB2001DF9E7 /* Expandable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expandable.swift; sourceTree = "<group>"; };
|
||||
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellViewModel.swift; sourceTree = "<group>"; };
|
||||
32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutType.swift; sourceTree = "<group>"; };
|
||||
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellRegisterer.swift; sourceTree = "<group>"; };
|
||||
50E858571DB153F500A9AA55 /* TableKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableKit.swift; sourceTree = "<group>"; };
|
||||
DA9EA7561D0B679A0021F650 /* TableKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TableKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = "<group>"; };
|
||||
DA9EA7A71D0EC2C90021F650 /* HeightStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeightStrategy.swift; sourceTree = "<group>"; };
|
||||
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TablePrototypeCellHeightCalculator.swift; sourceTree = "<group>"; };
|
||||
DA9EA7A81D0EC2C90021F650 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
|
||||
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellAction.swift; sourceTree = "<group>"; };
|
||||
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableDirector.swift; sourceTree = "<group>"; };
|
||||
DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRow.swift; sourceTree = "<group>"; };
|
||||
DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowAction.swift; sourceTree = "<group>"; };
|
||||
DA9EA7AD1D0EC2C90021F650 /* TableRowBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowBuilder.swift; sourceTree = "<group>"; };
|
||||
DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableSection.swift; sourceTree = "<group>"; };
|
||||
DA9EA7B91D0EC34E0021F650 /* TableKit.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TableKit.plist; sourceTree = "<group>"; };
|
||||
DA9EA7BA1D0EC34E0021F650 /* TableKitTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TableKitTests.plist; sourceTree = "<group>"; };
|
||||
|
|
@ -88,15 +102,22 @@
|
|||
DA9EA7A51D0EC2B90021F650 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */,
|
||||
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */,
|
||||
DA9EA7A81D0EC2C90021F650 /* Operators.swift */,
|
||||
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */,
|
||||
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */,
|
||||
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */,
|
||||
50E858571DB153F500A9AA55 /* TableKit.swift */,
|
||||
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */,
|
||||
DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */,
|
||||
DA9EA7AD1D0EC2C90021F650 /* TableRowBuilder.swift */,
|
||||
DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */,
|
||||
DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */,
|
||||
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */,
|
||||
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */,
|
||||
DA9EA7A81D0EC2C90021F650 /* Operators.swift */,
|
||||
DA9EA7A71D0EC2C90021F650 /* HeightStrategy.swift */,
|
||||
3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */,
|
||||
3201E78721BE9EB2001DF9E7 /* Expandable.swift */,
|
||||
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */,
|
||||
32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */,
|
||||
2CBFA2F421F692F100147B56 /* ExpandableState.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -174,14 +195,16 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0730;
|
||||
LastUpgradeCheck = 0730;
|
||||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = "Max Sokolov";
|
||||
TargetAttributes = {
|
||||
DA9EA7551D0B679A0021F650 = {
|
||||
CreatedOnToolsVersion = 7.3;
|
||||
LastSwiftMigration = 1000;
|
||||
};
|
||||
DA9EA7C31D0EC45F0021F650 = {
|
||||
CreatedOnToolsVersion = 7.3;
|
||||
LastSwiftMigration = 1000;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -190,6 +213,7 @@
|
|||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
);
|
||||
mainGroup = DA9EA74C1D0B679A0021F650;
|
||||
|
|
@ -225,15 +249,22 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3201E78A21BE9ED4001DF9E7 /* ExpandableCellViewModel.swift in Sources */,
|
||||
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */,
|
||||
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */,
|
||||
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */,
|
||||
3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */,
|
||||
2CBFA2F521F692F100147B56 /* ExpandableState.swift in Sources */,
|
||||
DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */,
|
||||
DA9EA7B01D0EC2C90021F650 /* HeightStrategy.swift in Sources */,
|
||||
DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */,
|
||||
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */,
|
||||
DA9EA7B51D0EC2C90021F650 /* TableRowAction.swift in Sources */,
|
||||
DA9EA7B21D0EC2C90021F650 /* TableCellAction.swift in Sources */,
|
||||
32BDFE9F21C167F400D0BBB4 /* LayoutType.swift in Sources */,
|
||||
3201E78621BE9E25001DF9E7 /* UITableViewCell+Extensions.swift in Sources */,
|
||||
DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */,
|
||||
DA9EA7B41D0EC2C90021F650 /* TableRow.swift in Sources */,
|
||||
DA9EA7B61D0EC2C90021F650 /* TableRowBuilder.swift in Sources */,
|
||||
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -265,13 +296,23 @@
|
|||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
|
|
@ -299,6 +340,7 @@
|
|||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
|
|
@ -314,13 +356,23 @@
|
|||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
|
|
@ -340,6 +392,8 @@
|
|||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
|
@ -351,6 +405,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
|
|
@ -363,6 +418,7 @@
|
|||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -370,6 +426,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
|
|
@ -381,6 +438,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKit;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
@ -392,6 +450,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -403,6 +462,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
LastUpgradeVersion = "1000"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ class TestController: UITableViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableDirector = TableDirector(tableView: tableView)
|
||||
tableDirector.register(TestTableViewCell.self)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,22 +42,22 @@ struct TestTableViewCellOptions {
|
|||
static let CellAction: String = "CellAction"
|
||||
static let CellActionUserInfoKey: String = "CellActionUserInfoKey"
|
||||
static let CellActionUserInfoValue: String = "CellActionUserInfoValue"
|
||||
static let EstimatedHeight: Float = 255
|
||||
static let EstimatedHeight: CGFloat = 255
|
||||
}
|
||||
|
||||
class TestTableViewCell: UITableViewCell, ConfigurableCell {
|
||||
|
||||
typealias T = TestData
|
||||
|
||||
static func reusableIdentifier() -> String {
|
||||
static var estimatedHeight: CGFloat? {
|
||||
return TestTableViewCellOptions.EstimatedHeight
|
||||
}
|
||||
|
||||
static var reuseIdentifier: String {
|
||||
return TestTableViewCellOptions.ReusableIdentifier
|
||||
}
|
||||
|
||||
static func estimatedHeight() -> Float {
|
||||
return TestTableViewCellOptions.EstimatedHeight
|
||||
}
|
||||
|
||||
func configure(item: T, isPrototype: Bool) {
|
||||
func configure(with item: T) {
|
||||
textLabel?.text = item.title
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +66,7 @@ class TestTableViewCell: UITableViewCell, ConfigurableCell {
|
|||
}
|
||||
}
|
||||
|
||||
class TabletTests: XCTestCase {
|
||||
class TableKitTests: XCTestCase {
|
||||
|
||||
var testController: TestController!
|
||||
|
||||
|
|
@ -75,7 +74,10 @@ class TabletTests: XCTestCase {
|
|||
super.setUp()
|
||||
|
||||
testController = TestController()
|
||||
testController.view.hidden = false
|
||||
testController.tableView.frame = UIScreen.main.bounds
|
||||
testController.tableView.isHidden = false
|
||||
testController.tableView.setNeedsLayout()
|
||||
testController.tableView.layoutIfNeeded()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
|
@ -95,15 +97,15 @@ class TabletTests: XCTestCase {
|
|||
|
||||
let data = TestData(title: "title")
|
||||
|
||||
let row = TableRow<TestData, TestTableViewCell>(item: data)
|
||||
let row = TableRow<TestTableViewCell>(item: data)
|
||||
|
||||
testController.tableDirector += row
|
||||
testController.tableView.reloadData()
|
||||
|
||||
XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section")
|
||||
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section")
|
||||
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section")
|
||||
|
||||
let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell
|
||||
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
|
||||
XCTAssertNotNil(cell)
|
||||
XCTAssertTrue(cell?.textLabel?.text == data.title)
|
||||
}
|
||||
|
|
@ -112,17 +114,17 @@ class TabletTests: XCTestCase {
|
|||
|
||||
let data = [TestData(title: "1"), TestData(title: "2"), TestData(title: "3")]
|
||||
|
||||
let rows: [Row] = data.map({ TableRow<TestData, TestTableViewCell>(item: $0) })
|
||||
let rows: [Row] = data.map({ TableRow<TestTableViewCell>(item: $0) })
|
||||
|
||||
testController.tableDirector += rows
|
||||
testController.tableView.reloadData()
|
||||
|
||||
XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section")
|
||||
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section")
|
||||
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == data.count, "Table view should have certain number of rows in a section")
|
||||
|
||||
for (index, element) in data.enumerate() {
|
||||
for (index, element) in data.enumerated() {
|
||||
|
||||
let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TestTableViewCell
|
||||
let cell = testController.tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? TestTableViewCell
|
||||
XCTAssertNotNil(cell)
|
||||
XCTAssertTrue(cell?.textLabel?.text == element.title)
|
||||
}
|
||||
|
|
@ -130,7 +132,7 @@ class TabletTests: XCTestCase {
|
|||
|
||||
func testTableSectionCreatesSectionWithHeaderAndFooterTitles() {
|
||||
|
||||
let row = TableRow<TestData, TestTableViewCell>(item: TestData(title: "title"))
|
||||
let row = TableRow<TestTableViewCell>(item: TestData(title: "title"))
|
||||
|
||||
let sectionHeaderTitle = "Header Title"
|
||||
let sectionFooterTitle = "Footer Title"
|
||||
|
|
@ -140,7 +142,7 @@ class TabletTests: XCTestCase {
|
|||
testController.tableDirector += section
|
||||
testController.tableView.reloadData()
|
||||
|
||||
XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section")
|
||||
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section")
|
||||
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section")
|
||||
|
||||
XCTAssertTrue(testController.tableView.dataSource?.tableView?(testController.tableView, titleForHeaderInSection: 0) == sectionHeaderTitle)
|
||||
|
|
@ -149,7 +151,7 @@ class TabletTests: XCTestCase {
|
|||
|
||||
func testTableSectionCreatesSectionWithHeaderAndFooterViews() {
|
||||
|
||||
let row = TableRow<TestData, TestTableViewCell>(item: TestData(title: "title"))
|
||||
let row = TableRow<TestTableViewCell>(item: TestData(title: "title"))
|
||||
|
||||
let sectionHeaderView = UIView()
|
||||
let sectionFooterView = UIView()
|
||||
|
|
@ -160,7 +162,7 @@ class TabletTests: XCTestCase {
|
|||
testController.tableDirector += section
|
||||
testController.tableView.reloadData()
|
||||
|
||||
XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section")
|
||||
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section")
|
||||
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section")
|
||||
|
||||
XCTAssertTrue(testController.tableView.delegate?.tableView?(testController.tableView, viewForHeaderInSection: 0) == sectionHeaderView)
|
||||
|
|
@ -169,26 +171,75 @@ class TabletTests: XCTestCase {
|
|||
|
||||
func testRowBuilderCustomActionInvokedAndSentUserInfo() {
|
||||
|
||||
let expectation = expectationWithDescription("cell action")
|
||||
let expectation = self.expectation(description: "cell action")
|
||||
|
||||
let row = TableRow<TestData, TestTableViewCell>(item: TestData(title: "title"))
|
||||
.action(TableRowAction(.custom(TestTableViewCellOptions.CellAction)) { (data) in
|
||||
let row = TableRow<TestTableViewCell>(item: TestData(title: "title"))
|
||||
.on(TableRowAction(.custom(TestTableViewCellOptions.CellAction)) { (data) in
|
||||
|
||||
XCTAssertNotNil(data.cell, "Action data should have a cell")
|
||||
|
||||
expectation.fulfill()
|
||||
})
|
||||
|
||||
testController.view.hidden = false
|
||||
testController.view.isHidden = false
|
||||
testController.tableDirector += row
|
||||
testController.tableView.reloadData()
|
||||
|
||||
let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell
|
||||
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
|
||||
|
||||
XCTAssertNotNil(cell, "Cell should exists and should be TestTableViewCell")
|
||||
|
||||
cell?.raiseAction()
|
||||
|
||||
waitForExpectationsWithTimeout(1.0, handler: nil)
|
||||
waitForExpectations(timeout: 1.0, handler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func testReplaceSectionOnExistingIndex() {
|
||||
|
||||
let row1 = TableRow<TestTableViewCell>(item: TestData(title: "title1"))
|
||||
let row2 = TableRow<TestTableViewCell>(item: TestData(title: "title2"))
|
||||
|
||||
let section1 = TableSection(headerView: nil, footerView: nil, rows: nil)
|
||||
section1 += row1
|
||||
|
||||
let section2 = TableSection(headerView: nil, footerView: nil, rows: nil)
|
||||
section2 += row2
|
||||
|
||||
testController.tableDirector += section1
|
||||
testController.tableView.reloadData()
|
||||
|
||||
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
|
||||
XCTAssertTrue(cell?.textLabel?.text == "title1")
|
||||
|
||||
testController.tableDirector.replaceSection(at: 0, with: section2)
|
||||
testController.tableView.reloadData()
|
||||
|
||||
let cell1 = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
|
||||
XCTAssertTrue(cell1?.textLabel?.text == "title2")
|
||||
}
|
||||
|
||||
func testReplaceSectionOnWrongIndex() {
|
||||
|
||||
let row1 = TableRow<TestTableViewCell>(item: TestData(title: "title1"))
|
||||
let row2 = TableRow<TestTableViewCell>(item: TestData(title: "title2"))
|
||||
|
||||
let section1 = TableSection(headerView: nil, footerView: nil, rows: nil)
|
||||
section1 += row1
|
||||
|
||||
let section2 = TableSection(headerView: nil, footerView: nil, rows: nil)
|
||||
section2 += row2
|
||||
|
||||
testController.tableDirector += section1
|
||||
testController.tableView.reloadData()
|
||||
|
||||
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
|
||||
XCTAssertTrue(cell?.textLabel?.text == "title1")
|
||||
|
||||
testController.tableDirector.replaceSection(at: 33, with: section2)
|
||||
testController.tableView.reloadData()
|
||||
|
||||
let cell1 = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
|
||||
XCTAssertTrue(cell1?.textLabel?.text == "title1")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue