Compare commits
115 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 |
|
|
@ -1 +1 @@
|
||||||
3.0
|
5.7
|
||||||
|
|
|
||||||
16
.travis.yml
16
.travis.yml
|
|
@ -1,5 +1,5 @@
|
||||||
language: objective-c
|
language: objective-c
|
||||||
osx_image: xcode8
|
osx_image: xcode11
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
|
@ -7,20 +7,18 @@ env:
|
||||||
global:
|
global:
|
||||||
- LC_CTYPE=en_US.UTF-8
|
- LC_CTYPE=en_US.UTF-8
|
||||||
- LANG=en_US.UTF-8
|
- LANG=en_US.UTF-8
|
||||||
- IOS_SDK=iphonesimulator10.0
|
- IOS_SDK=iphonesimulator13.0
|
||||||
- SCHEME_IOS="TableKit"
|
- SCHEME_IOS="TableKit"
|
||||||
- PROJECT_FRAMEWORK="TableKit.xcodeproj"
|
- PROJECT_FRAMEWORK="TableKit.xcodeproj"
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
- DESTINATION="OS=9.0,name=iPhone 6" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
- DESTINATION="OS=10.3.1,name=iPhone 5" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
||||||
- DESTINATION="OS=9.1,name=iPhone 6 Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
- DESTINATION="OS=11.1,name=iPhone 6" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
||||||
- DESTINATION="OS=9.2,name=iPhone 6S" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
- DESTINATION="OS=12.0,name=iPhone 7 Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
||||||
- DESTINATION="OS=9.3,name=iPhone 6S Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
- DESTINATION="OS=13.0,name=iPhone 11" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
||||||
- DESTINATION="OS=10.0,name=iPhone 5" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
|
||||||
- DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- set -o pipefail
|
- set -o pipefail
|
||||||
- xcodebuild -version
|
- xcodebuild -version
|
||||||
- xcodebuild -showsdks
|
- xcodebuild -showsdks
|
||||||
- xcodebuild -project "$PROJECT_FRAMEWORK" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
|
- 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
|
||||||
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -2,7 +2,28 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [2.1.0](https://github.com/maxsokolov/TableKit/releases/tag/1.4.0)
|
## [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.
|
Released on 2016-10-19.
|
||||||
- `action` method was deprecated on TableRow. Use `on` instead.
|
- `action` method was deprecated on TableRow. Use `on` instead.
|
||||||
- Support multiple actions with same type on row.
|
- Support multiple actions with same type on row.
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,7 @@
|
||||||
//
|
|
||||||
// AppDelegate.swift
|
|
||||||
// TabletDemo
|
|
||||||
//
|
|
||||||
// Created by Max Sokolov on 08/11/15.
|
|
||||||
// Copyright © 2015 Tablet. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
var window: UIWindow?
|
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 UIKit
|
||||||
import TableKit
|
import TableKit
|
||||||
|
|
||||||
|
|
@ -13,27 +5,66 @@ class AutolayoutCellsController: UIViewController {
|
||||||
|
|
||||||
@IBOutlet weak var tableView: UITableView! {
|
@IBOutlet weak var tableView: UITableView! {
|
||||||
didSet {
|
didSet {
|
||||||
tableDirector = TableDirector(tableView: tableView)
|
tableDirector = TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true)
|
||||||
tableDirector.shouldUsePrototypeCellHeightCalculation = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var tableDirector: TableDirector!
|
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() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
title = "Autolayout cells"
|
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
|
var rows = 0
|
||||||
while rows <= 1000 {
|
while rows <= 20 {
|
||||||
rows += 1
|
rows += 1
|
||||||
|
|
||||||
let row = TableRow<AutolayoutTableViewCell>(item: ())
|
let row = TableRow<AutolayoutTableViewCell>(item: randomString(length: randomInt(min: 20, max: 100)))
|
||||||
section += row
|
section += row
|
||||||
}
|
}
|
||||||
|
|
||||||
tableDirector += section
|
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 UIKit
|
||||||
import TableKit
|
import TableKit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
//
|
|
||||||
// NibCellsController.swift
|
|
||||||
// TableKitDemo
|
|
||||||
//
|
|
||||||
// Created by Max Sokolov on 18/06/16.
|
|
||||||
// Copyright © 2016 Tablet. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import TableKit
|
import TableKit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,32 +1,23 @@
|
||||||
//
|
|
||||||
// AutolayoutTableViewCell.swift
|
|
||||||
// TabletDemo
|
|
||||||
//
|
|
||||||
// Created by Max Sokolov on 24/05/16.
|
|
||||||
// Copyright © 2016 Tablet. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import TableKit
|
import TableKit
|
||||||
|
|
||||||
private let LoremIpsumTitle = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"
|
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 {
|
class AutolayoutTableViewCell: UITableViewCell, ConfigurableCell {
|
||||||
|
|
||||||
typealias T = Void
|
typealias T = String
|
||||||
|
|
||||||
@IBOutlet var titleLabel: UILabel!
|
@IBOutlet var titleLabel: UILabel!
|
||||||
@IBOutlet var subtitleLabel: UILabel!
|
@IBOutlet var subtitleLabel: UILabel!
|
||||||
|
|
||||||
static var estimatedHeight: CGFloat? {
|
static var estimatedHeight: CGFloat? {
|
||||||
return 700
|
return 150
|
||||||
}
|
}
|
||||||
|
|
||||||
func configure(with string: T) {
|
func configure(with string: T) {
|
||||||
|
|
||||||
titleLabel.text = LoremIpsumTitle
|
titleLabel.text = LoremIpsumTitle
|
||||||
subtitleLabel.text = LoremIpsumBody
|
subtitleLabel.text = string
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
|
|
@ -37,4 +28,4 @@ class AutolayoutTableViewCell: UITableViewCell, ConfigurableCell {
|
||||||
titleLabel.preferredMaxLayoutWidth = titleLabel.bounds.size.width
|
titleLabel.preferredMaxLayoutWidth = titleLabel.bounds.size.width
|
||||||
subtitleLabel.preferredMaxLayoutWidth = subtitleLabel.bounds.size.width
|
subtitleLabel.preferredMaxLayoutWidth = subtitleLabel.bounds.size.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
//
|
|
||||||
// ConfigurableTableViewCell.swift
|
|
||||||
// TableKitDemo
|
|
||||||
//
|
|
||||||
// Created by Max Sokolov on 18/06/16.
|
|
||||||
// Copyright © 2016 Tablet. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import TableKit
|
import TableKit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
//
|
|
||||||
// NibTableViewCell.swift
|
|
||||||
// TableKitDemo
|
|
||||||
//
|
|
||||||
// Created by Max Sokolov on 18/06/16.
|
|
||||||
// Copyright © 2016 Tablet. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import TableKit
|
import TableKit
|
||||||
|
|
||||||
|
|
@ -20,4 +12,4 @@ class NibTableViewCell: UITableViewCell, ConfigurableCell {
|
||||||
func configure(with number: Int) {
|
func configure(with number: Int) {
|
||||||
titleLabel.text = "\(number)"
|
titleLabel.text = "\(number)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* 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 */; };
|
DA08A0531CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA08A0521CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift */; };
|
||||||
DA55465D1D1569CC00AA83EE /* AutolayoutCellsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA55465C1D1569CC00AA83EE /* AutolayoutCellsController.swift */; };
|
DA55465D1D1569CC00AA83EE /* AutolayoutCellsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA55465C1D1569CC00AA83EE /* AutolayoutCellsController.swift */; };
|
||||||
DA5546601D156A4F00AA83EE /* ConfigurableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA55465F1D156A4F00AA83EE /* ConfigurableTableViewCell.swift */; };
|
DA5546601D156A4F00AA83EE /* ConfigurableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA55465F1D156A4F00AA83EE /* ConfigurableTableViewCell.swift */; };
|
||||||
|
|
@ -61,6 +63,8 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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>"; };
|
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>"; };
|
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>"; };
|
DA55465F1D156A4F00AA83EE /* ConfigurableTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -194,6 +198,8 @@
|
||||||
DA08A0521CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift */,
|
DA08A0521CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift */,
|
||||||
DA5546631D15762000AA83EE /* NibTableViewCell.swift */,
|
DA5546631D15762000AA83EE /* NibTableViewCell.swift */,
|
||||||
DA5546651D15765900AA83EE /* NibTableViewCell.xib */,
|
DA5546651D15765900AA83EE /* NibTableViewCell.xib */,
|
||||||
|
5079ADE21FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift */,
|
||||||
|
5079ADE51FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -227,13 +233,13 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 0720;
|
LastSwiftUpdateCheck = 0720;
|
||||||
LastUpgradeCheck = 0800;
|
LastUpgradeCheck = 1000;
|
||||||
ORGANIZATIONNAME = Tablet;
|
ORGANIZATIONNAME = Tablet;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
DAB7EB261BEF787300D2AD5E = {
|
DAB7EB261BEF787300D2AD5E = {
|
||||||
CreatedOnToolsVersion = 7.0.1;
|
CreatedOnToolsVersion = 7.0.1;
|
||||||
DevelopmentTeam = Z48R734SJX;
|
DevelopmentTeam = Z48R734SJX;
|
||||||
LastSwiftMigration = 0800;
|
LastSwiftMigration = 1000;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -242,6 +248,7 @@
|
||||||
developmentRegion = English;
|
developmentRegion = English;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
|
English,
|
||||||
en,
|
en,
|
||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
|
|
@ -284,6 +291,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
DAC2D5CF1C9D30A7009E9C19 /* Main.storyboard in Resources */,
|
DAC2D5CF1C9D30A7009E9C19 /* Main.storyboard in Resources */,
|
||||||
|
5079ADE61FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib in Resources */,
|
||||||
DA5546661D15765900AA83EE /* NibTableViewCell.xib in Resources */,
|
DA5546661D15765900AA83EE /* NibTableViewCell.xib in Resources */,
|
||||||
DAC2D5D01C9D30A7009E9C19 /* LaunchScreen.storyboard in Resources */,
|
DAC2D5D01C9D30A7009E9C19 /* LaunchScreen.storyboard in Resources */,
|
||||||
DAC2D69C1C9E75E3009E9C19 /* Assets.xcassets in Resources */,
|
DAC2D69C1C9E75E3009E9C19 /* Assets.xcassets in Resources */,
|
||||||
|
|
@ -301,6 +309,7 @@
|
||||||
DA55465D1D1569CC00AA83EE /* AutolayoutCellsController.swift in Sources */,
|
DA55465D1D1569CC00AA83EE /* AutolayoutCellsController.swift in Sources */,
|
||||||
DA5546681D15771D00AA83EE /* NibCellsController.swift in Sources */,
|
DA5546681D15771D00AA83EE /* NibCellsController.swift in Sources */,
|
||||||
DAC2D5CA1C9D303E009E9C19 /* AppDelegate.swift in Sources */,
|
DAC2D5CA1C9D303E009E9C19 /* AppDelegate.swift in Sources */,
|
||||||
|
5079ADE31FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift in Sources */,
|
||||||
DA5546641D15762000AA83EE /* NibTableViewCell.swift in Sources */,
|
DA5546641D15762000AA83EE /* NibTableViewCell.swift in Sources */,
|
||||||
DA5546601D156A4F00AA83EE /* ConfigurableTableViewCell.swift in Sources */,
|
DA5546601D156A4F00AA83EE /* ConfigurableTableViewCell.swift in Sources */,
|
||||||
DA08A0531CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift in Sources */,
|
DA08A0531CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift in Sources */,
|
||||||
|
|
@ -326,14 +335,22 @@
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_INT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
|
@ -361,6 +378,7 @@
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -372,14 +390,22 @@
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_INT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
|
@ -400,6 +426,7 @@
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
@ -417,7 +444,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
|
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
|
||||||
PRODUCT_NAME = TableKitDemo;
|
PRODUCT_NAME = TableKitDemo;
|
||||||
PROVISIONING_PROFILE = "";
|
PROVISIONING_PROFILE = "";
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -434,7 +461,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
|
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
|
||||||
PRODUCT_NAME = TableKitDemo;
|
PRODUCT_NAME = TableKitDemo;
|
||||||
PROVISIONING_PROFILE = "";
|
PROVISIONING_PROFILE = "";
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
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
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "TableKit"
|
name: "TableKit",
|
||||||
)
|
|
||||||
|
products: [
|
||||||
|
.library(
|
||||||
|
name: "TableKit",
|
||||||
|
targets: ["TableKit"]),
|
||||||
|
],
|
||||||
|
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "TableKit",
|
||||||
|
path: "Sources")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
|
||||||
90
README.md
90
README.md
|
|
@ -1,10 +1,10 @@
|
||||||
#TableKit
|
# TableKit
|
||||||
|
|
||||||
<p align="left">
|
<p align="left">
|
||||||
<a href="https://travis-ci.org/maxsokolov/TableKit"><img src="https://api.travis-ci.org/maxsokolov/TableKit.svg" alt="Build Status" /></a>
|
<a href="https://travis-ci.org/maxsokolov/TableKit"><img src="https://api.travis-ci.org/maxsokolov/TableKit.svg" alt="Build Status" /></a>
|
||||||
<a href="https://developer.apple.com/swift"><img src="https://img.shields.io/badge/Swift_3.0-compatible-4BC51D.svg?style=flat" alt="Swift 3.0 compatible" /></a>
|
<a href="https://developer.apple.com/swift"><img src="https://img.shields.io/badge/Swift_5.1-compatible-4BC51D.svg?style=flat" alt="Swift 5.1 compatible" /></a>
|
||||||
<a href="https://github.com/Carthage/Carthage"><img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat" alt="Carthage compatible" /></a>
|
<a href="https://github.com/Carthage/Carthage"><img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat" alt="Carthage compatible" /></a>
|
||||||
<a href="https://cocoapods.org/pods/tablekit"><img src="https://img.shields.io/badge/pod-2.2.0-blue.svg" alt="CocoaPods compatible" /></a>
|
<a href="https://cocoapods.org/pods/tablekit"><img src="https://img.shields.io/badge/pod-2.11.0-blue.svg" alt="CocoaPods compatible" /></a>
|
||||||
<img src="https://img.shields.io/badge/platform-iOS-blue.svg?style=flat" alt="Platform iOS" />
|
<img src="https://img.shields.io/badge/platform-iOS-blue.svg?style=flat" alt="Platform iOS" />
|
||||||
<a href="https://raw.githubusercontent.com/maxsokolov/tablekit/master/LICENSE"><img src="http://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="License: MIT" /></a>
|
<a href="https://raw.githubusercontent.com/maxsokolov/tablekit/master/LICENSE"><img src="http://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="License: MIT" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -53,29 +53,29 @@ Done. Your table is ready. Your cells have to conform to `ConfigurableCell` prot
|
||||||
```swift
|
```swift
|
||||||
class StringTableViewCell: UITableViewCell, ConfigurableCell {
|
class StringTableViewCell: UITableViewCell, ConfigurableCell {
|
||||||
|
|
||||||
func configure(with string: String) {
|
func configure(with string: String) {
|
||||||
|
|
||||||
textLabel?.text = string
|
textLabel?.text = string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserTableViewCell: UITableViewCell, ConfigurableCell {
|
class UserTableViewCell: UITableViewCell, ConfigurableCell {
|
||||||
|
|
||||||
static var estimatedHeight: CGFloat? {
|
static var estimatedHeight: CGFloat? {
|
||||||
return 100
|
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) {
|
// 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
|
textLabel?.text = user.name
|
||||||
detailTextLabel?.text = "Rating: \(user.rating)"
|
detailTextLabel?.text = "Rating: \(user.rating)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
You could have as many rows and sections as you need.
|
You could have as many rows and sections as you need.
|
||||||
|
|
@ -86,12 +86,12 @@ It nice to have some actions that related to your cells:
|
||||||
```swift
|
```swift
|
||||||
let action = TableRowAction<StringTableViewCell>(.click) { (options) 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
|
||||||
|
|
||||||
// options.cell - StringTableViewCell?
|
// options.cell - StringTableViewCell?
|
||||||
// options.item - String
|
// options.item - String
|
||||||
// options.indexPath - IndexPath
|
// options.indexPath - IndexPath
|
||||||
// options.userInfo - [AnyHashable: Any]?
|
// options.userInfo - [AnyHashable: Any]?
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = TableRow<StringTableViewCell>(item: "some", actions: [action])
|
let row = TableRow<StringTableViewCell>(item: "some", actions: [action])
|
||||||
|
|
@ -99,12 +99,12 @@ let row = TableRow<StringTableViewCell>(item: "some", actions: [action])
|
||||||
Or, using nice chaining approach:
|
Or, using nice chaining approach:
|
||||||
```swift
|
```swift
|
||||||
let row = TableRow<StringTableViewCell>(item: "some")
|
let row = TableRow<StringTableViewCell>(item: "some")
|
||||||
.on(.click) { (options) in
|
.on(.click) { (options) in
|
||||||
|
|
||||||
}
|
}
|
||||||
.on(.shouldHighlight) { (options) -> Bool in
|
.on(.shouldHighlight) { (options) -> Bool in
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
You could find all available actions [here](Sources/TableRowAction.swift).
|
You could find all available actions [here](Sources/TableRowAction.swift).
|
||||||
|
|
||||||
|
|
@ -119,10 +119,10 @@ struct MyActions {
|
||||||
|
|
||||||
class MyTableViewCell: UITableViewCell, ConfigurableCell {
|
class MyTableViewCell: UITableViewCell, ConfigurableCell {
|
||||||
|
|
||||||
@IBAction func myButtonClicked(sender: UIButton) {
|
@IBAction func myButtonClicked(sender: UIButton) {
|
||||||
|
|
||||||
TableCellAction(key: MyActions.ButtonClicked, sender: self).invoke()
|
TableCellAction(key: MyActions.ButtonClicked, sender: self).invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
And handle them accordingly:
|
And handle them accordingly:
|
||||||
|
|
@ -158,31 +158,31 @@ By default TableKit relies on <a href="https://developer.apple.com/library/ios/d
|
||||||
```swift
|
```swift
|
||||||
class StringTableViewCell: UITableViewCell, ConfigurableCell {
|
class StringTableViewCell: UITableViewCell, ConfigurableCell {
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
static var estimatedHeight: CGFloat? {
|
static var estimatedHeight: CGFloat? {
|
||||||
return 255
|
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 cells 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
|
```swift
|
||||||
tableDirector.shouldUsePrototypeCellHeightCalculation = true
|
let tableDirector = TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true)
|
||||||
```
|
```
|
||||||
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:
|
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
|
```swift
|
||||||
class ImageTableViewCell: UITableViewCell, ConfigurableCell {
|
class ImageTableViewCell: UITableViewCell, ConfigurableCell {
|
||||||
|
|
||||||
func configure(with url: NSURL) {
|
func configure(with url: NSURL) {
|
||||||
|
|
||||||
loadImageAsync(url: url, imageView: imageView)
|
loadImageAsync(url: url, imageView: imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
contentView.layoutIfNeeded()
|
contentView.layoutIfNeeded()
|
||||||
multilineLabel.preferredMaxLayoutWidth = multilineLabel.bounds.size.width
|
multilineLabel.preferredMaxLayoutWidth = multilineLabel.bounds.size.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -204,7 +204,7 @@ tableDirector += rows
|
||||||
Done, your table is ready.
|
Done, your table is ready.
|
||||||
## Automatic cell registration
|
## Automatic cell registration
|
||||||
|
|
||||||
TableKit can register your cells in a table view automatically. In case if your reusable cell id mathces cell's xib name:
|
TableKit can register your cells in a table view automatically. In case if your reusable cell id matches cell's xib name:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
MyTableViewCell.swift
|
MyTableViewCell.swift
|
||||||
|
|
@ -233,12 +233,12 @@ Clone the repo and drag files from `Sources` folder into your Xcode project.
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
- iOS 8.0
|
- iOS 8.0
|
||||||
- Xcode 8.0
|
- Xcode 9.0
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
Keep eye on [changes](CHANGELOG.md).
|
Keep an eye on [changes](CHANGELOG.md).
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
TableKit is available under the MIT license. See LICENSE for details.
|
TableKit is available under the MIT license. See LICENSE for details.
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,18 @@ import UIKit
|
||||||
|
|
||||||
public protocol ConfigurableCell {
|
public protocol ConfigurableCell {
|
||||||
|
|
||||||
associatedtype T
|
associatedtype CellData
|
||||||
|
|
||||||
static var reuseIdentifier: String { get }
|
static var reuseIdentifier: String { get }
|
||||||
|
|
||||||
static var estimatedHeight: CGFloat? { get }
|
static var estimatedHeight: CGFloat? { get }
|
||||||
|
|
||||||
static var defaultHeight: CGFloat? { get }
|
static var defaultHeight: CGFloat? { get }
|
||||||
|
|
||||||
func configure(with _: T)
|
static var layoutType: LayoutType { get }
|
||||||
|
|
||||||
|
func configure(with _: CellData)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension ConfigurableCell where Self: UITableViewCell {
|
public extension ConfigurableCell where Self: UITableViewCell {
|
||||||
|
|
@ -44,4 +49,9 @@ public extension ConfigurableCell where Self: UITableViewCell {
|
||||||
static var defaultHeight: CGFloat? {
|
static var defaultHeight: CGFloat? {
|
||||||
return nil
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -27,13 +27,13 @@ import UIKit
|
||||||
open class TableCellAction {
|
open class TableCellAction {
|
||||||
|
|
||||||
/// The cell that triggers an action.
|
/// The cell that triggers an action.
|
||||||
open let cell: UITableViewCell
|
public let cell: UITableViewCell
|
||||||
|
|
||||||
/// The action unique key.
|
/// The action unique key.
|
||||||
open let key: String
|
public let key: String
|
||||||
|
|
||||||
/// The custom user info.
|
/// The custom user info.
|
||||||
open let userInfo: [AnyHashable: Any]?
|
public let userInfo: [AnyHashable: Any]?
|
||||||
|
|
||||||
public init(key: String, sender: UITableViewCell, userInfo: [AnyHashable: Any]? = nil) {
|
public init(key: String, sender: UITableViewCell, userInfo: [AnyHashable: Any]? = nil) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class TableCellRegisterer {
|
||||||
// in that case we could register nib
|
// in that case we could register nib
|
||||||
if let _ = bundle.path(forResource: reuseIdentifier, ofType: "nib") {
|
if let _ = bundle.path(forResource: reuseIdentifier, ofType: "nib") {
|
||||||
tableView?.register(UINib(nibName: reuseIdentifier, bundle: bundle), forCellReuseIdentifier: reuseIdentifier)
|
tableView?.register(UINib(nibName: reuseIdentifier, bundle: bundle), forCellReuseIdentifier: reuseIdentifier)
|
||||||
// otherwise, register cell class
|
// otherwise, register cell class
|
||||||
} else {
|
} else {
|
||||||
tableView?.register(cellType, forCellReuseIdentifier: reuseIdentifier)
|
tableView?.register(cellType, forCellReuseIdentifier: reuseIdentifier)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,10 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
|
|
||||||
private weak var scrollDelegate: UIScrollViewDelegate?
|
private weak var scrollDelegate: UIScrollViewDelegate?
|
||||||
private var cellRegisterer: TableCellRegisterer?
|
private var cellRegisterer: TableCellRegisterer?
|
||||||
public var rowHeightCalculator: RowHeightCalculator?
|
public private(set) var rowHeightCalculator: RowHeightCalculator?
|
||||||
|
private var sectionsIndexTitlesIndexes: [Int]?
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Produced incorrect behaviour")
|
||||||
open var shouldUsePrototypeCellHeightCalculation: Bool = false {
|
open var shouldUsePrototypeCellHeightCalculation: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if shouldUsePrototypeCellHeightCalculation {
|
if shouldUsePrototypeCellHeightCalculation {
|
||||||
|
|
@ -46,21 +48,45 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
return sections.isEmpty
|
return sections.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(tableView: UITableView, scrollDelegate: UIScrollViewDelegate? = nil, shouldUseAutomaticCellRegistration: Bool = true) {
|
public init(
|
||||||
|
tableView: UITableView,
|
||||||
|
scrollDelegate: UIScrollViewDelegate? = nil,
|
||||||
|
shouldUseAutomaticCellRegistration: Bool = true,
|
||||||
|
cellHeightCalculator: RowHeightCalculator?)
|
||||||
|
{
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
if shouldUseAutomaticCellRegistration {
|
if shouldUseAutomaticCellRegistration {
|
||||||
self.cellRegisterer = TableCellRegisterer(tableView: tableView)
|
self.cellRegisterer = TableCellRegisterer(tableView: tableView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.rowHeightCalculator = cellHeightCalculator
|
||||||
self.scrollDelegate = scrollDelegate
|
self.scrollDelegate = scrollDelegate
|
||||||
self.tableView = tableView
|
self.tableView = tableView
|
||||||
self.tableView?.delegate = self
|
self.tableView?.delegate = self
|
||||||
self.tableView?.dataSource = self
|
self.tableView?.dataSource = self
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveAction), name: NSNotification.Name(rawValue: 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 {
|
deinit {
|
||||||
NotificationCenter.default.removeObserver(self)
|
NotificationCenter.default.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|
@ -69,11 +95,28 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
tableView?.reloadData()
|
tableView?.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Public
|
// 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
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func invoke(action: TableRowActionType, cell: UITableViewCell?, indexPath: IndexPath, userInfo: [AnyHashable: Any]? = nil) -> Any? {
|
open func invoke(
|
||||||
return sections[indexPath.section].rows[indexPath.row].invoke(action: action, cell: cell, path: indexPath, userInfo: userInfo)
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func responds(to selector: Selector) -> Bool {
|
open override func responds(to selector: Selector) -> Bool {
|
||||||
|
|
@ -81,15 +124,18 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func forwardingTarget(for selector: Selector) -> Any? {
|
open override func forwardingTarget(for selector: Selector) -> Any? {
|
||||||
return scrollDelegate?.responds(to: selector) == true ? scrollDelegate : super.forwardingTarget(for: selector)
|
return scrollDelegate?.responds(to: selector) == true
|
||||||
|
? scrollDelegate
|
||||||
|
: super.forwardingTarget(for: selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Internal -
|
// MARK: - Internal
|
||||||
|
|
||||||
func hasAction(_ action: TableRowActionType, atIndexPath indexPath: IndexPath) -> Bool {
|
func hasAction(_ action: TableRowActionType, atIndexPath indexPath: IndexPath) -> Bool {
|
||||||
return sections[indexPath.section].rows[indexPath.row].has(action: action)
|
guard let row = row(at: indexPath) else { return false }
|
||||||
|
return row.has(action: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
func didReceiveAction(_ notification: Notification) {
|
func didReceiveAction(_ notification: Notification) {
|
||||||
|
|
||||||
guard let action = notification.object as? TableCellAction, let indexPath = tableView?.indexPath(for: action.cell) else { return }
|
guard let action = notification.object as? TableCellAction, let indexPath = tableView?.indexPath(for: action.cell) else { return }
|
||||||
|
|
@ -97,7 +143,6 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Height
|
// MARK: - Height
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||||
|
|
||||||
let row = sections[indexPath.section].rows[indexPath.row]
|
let row = sections[indexPath.section].rows[indexPath.row]
|
||||||
|
|
@ -106,7 +151,10 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
cellRegisterer?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier)
|
cellRegisterer?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
return row.estimatedHeight ?? rowHeightCalculator?.estimatedHeight(forRow: row, at: indexPath) ?? UITableViewAutomaticDimension
|
return row.defaultHeight
|
||||||
|
?? row.estimatedHeight
|
||||||
|
?? rowHeightCalculator?.estimatedHeight(forRow: row, at: indexPath)
|
||||||
|
?? UITableView.automaticDimension
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||||
|
|
@ -119,16 +167,20 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
|
|
||||||
let rowHeight = invoke(action: .height, cell: nil, indexPath: indexPath) as? CGFloat
|
let rowHeight = invoke(action: .height, cell: nil, indexPath: indexPath) as? CGFloat
|
||||||
|
|
||||||
return rowHeight ?? row.defaultHeight ?? rowHeightCalculator?.height(forRow: row, at: indexPath) ?? UITableViewAutomaticDimension
|
return rowHeight
|
||||||
|
?? row.defaultHeight
|
||||||
|
?? rowHeightCalculator?.height(forRow: row, at: indexPath)
|
||||||
|
?? UITableView.automaticDimension
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: UITableViewDataSource - configuration
|
// MARK: UITableViewDataSource - configuration
|
||||||
|
|
||||||
open func numberOfSections(in tableView: UITableView) -> Int {
|
open func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
return sections.count
|
return sections.count
|
||||||
}
|
}
|
||||||
|
|
||||||
open 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
|
return sections[section].numberOfRows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,41 +204,78 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: UITableViewDataSource - section setup
|
// MARK: UITableViewDataSource - section setup
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
|
guard section < sections.count else { return nil }
|
||||||
|
|
||||||
return sections[section].headerTitle
|
return sections[section].headerTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||||
|
guard section < sections.count else { return nil }
|
||||||
|
|
||||||
return sections[section].footerTitle
|
return sections[section].footerTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: UITableViewDelegate - section setup
|
// MARK: UITableViewDelegate - section setup
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
|
guard section < sections.count else { return nil }
|
||||||
|
|
||||||
return sections[section].headerView
|
return sections[section].headerView
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||||
|
guard section < sections.count else { return nil }
|
||||||
|
|
||||||
return sections[section].footerView
|
return sections[section].footerView
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
|
guard section < sections.count else { return 0 }
|
||||||
|
|
||||||
let section = sections[section]
|
let section = sections[section]
|
||||||
return section.headerHeight ?? section.headerView?.frame.size.height ?? UITableViewAutomaticDimension
|
return section.headerHeight ?? section.headerView?.frame.size.height ?? UITableView.automaticDimension
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||||
|
guard section < sections.count else { return 0 }
|
||||||
|
|
||||||
let section = sections[section]
|
let section = sections[section]
|
||||||
return section.footerHeight ?? section.footerView?.frame.size.height ?? UITableViewAutomaticDimension
|
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
|
// MARK: UITableViewDelegate - actions
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
|
||||||
let cell = tableView.cellForRow(at: indexPath)
|
let cell = tableView.cellForRow(at: indexPath)
|
||||||
|
|
||||||
if invoke(action: .click, cell: cell, indexPath: indexPath) != nil {
|
if invoke(action: .click, cell: cell, indexPath: indexPath) != nil {
|
||||||
|
|
@ -203,83 +292,172 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
invoke(action: .willDisplay, cell: cell, indexPath: indexPath)
|
invoke(action: .willDisplay, cell: cell, indexPath: indexPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
|
invoke(action: .didEndDisplaying, cell: cell, indexPath: indexPath)
|
||||||
|
}
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
open func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
||||||
return invoke(action: .shouldHighlight, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? true
|
return invoke(action: .shouldHighlight, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? true
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
open func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||||
|
|
||||||
if hasAction(.willSelect, atIndexPath: indexPath) {
|
if hasAction(.willSelect, atIndexPath: indexPath) {
|
||||||
return invoke(action: .willSelect, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? IndexPath
|
return invoke(action: .willSelect, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? IndexPath
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexPath
|
return indexPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||||
|
if hasAction(.willDeselect, atIndexPath: indexPath) {
|
||||||
|
return invoke(action: .willDeselect, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? IndexPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexPath
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
open func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool
|
||||||
|
{
|
||||||
|
invoke(action: .shouldBeginMultipleSelection, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
open func tableView(
|
||||||
|
_ tableView: UITableView,
|
||||||
|
didBeginMultipleSelectionInteractionAt indexPath: IndexPath)
|
||||||
|
{
|
||||||
|
invoke(action: .didBeginMultipleSelection, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Row editing -
|
@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 {
|
open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||||
return sections[indexPath.section].rows[indexPath.row].isEditingAllowed(forIndexPath: indexPath)
|
return sections[indexPath.section].rows[indexPath.row].isEditingAllowed(forIndexPath: indexPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
|
open func tableView(_ tableView: UITableView,
|
||||||
return sections[indexPath.section].rows[indexPath.row].editingActions
|
leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||||
|
let currentRow = sections[indexPath.section].rows[indexPath.row]
|
||||||
|
let configuration = UISwipeActionsConfiguration(actions: currentRow.leadingContextualActions)
|
||||||
|
|
||||||
|
configuration.performsFirstActionWithFullSwipe = currentRow.performsFirstActionWithFullSwipe
|
||||||
|
|
||||||
|
return configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
open func tableView(_ tableView: UITableView,
|
||||||
|
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||||
|
let currentRow = sections[indexPath.section].rows[indexPath.row]
|
||||||
|
let configuration = UISwipeActionsConfiguration(actions: currentRow.trailingContextualActions)
|
||||||
|
|
||||||
|
configuration.performsFirstActionWithFullSwipe = currentRow.performsFirstActionWithFullSwipe
|
||||||
|
|
||||||
|
return configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
|
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 {
|
if editingStyle == .delete {
|
||||||
invoke(action: .clickDelete, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath)
|
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
|
// MARK: - Sections manipulation
|
||||||
extension TableDirector {
|
extension TableDirector {
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func append(section: TableSection) -> Self {
|
public func append(section: TableSection) -> Self {
|
||||||
|
|
||||||
append(sections: [section])
|
append(sections: [section])
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func append(sections: [TableSection]) -> Self {
|
public func append(sections: [TableSection]) -> Self {
|
||||||
|
|
||||||
self.sections.append(contentsOf: sections)
|
self.sections.append(contentsOf: sections)
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func append(rows: [Row]) -> Self {
|
public func append(rows: [Row]) -> Self {
|
||||||
|
|
||||||
append(section: TableSection(rows: rows))
|
append(section: TableSection(rows: rows))
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func insert(section: TableSection, atIndex index: Int) -> Self {
|
public func insert(section: TableSection, atIndex index: Int) -> Self {
|
||||||
|
|
||||||
sections.insert(section, at: index)
|
sections.insert(section, at: index)
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func delete(sectionAt index: Int) -> Self {
|
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)
|
sections.remove(at: index)
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func remove(sectionAt index: Int) -> Self {
|
public func remove(sectionAt index: Int) -> Self {
|
||||||
return delete(sectionAt: index)
|
return delete(sectionAt: index)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func clear() -> Self {
|
public func clear() -> Self {
|
||||||
|
|
||||||
rowHeightCalculator?.invalidate()
|
rowHeightCalculator?.invalidate()
|
||||||
sections.removeAll()
|
sections.removeAll()
|
||||||
|
|
||||||
|
|
@ -287,10 +465,9 @@ extension TableDirector {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - deprecated methods
|
// MARK: - deprecated methods
|
||||||
|
|
||||||
@available(*, deprecated, message: "Use 'delete(sectionAt:)' method instead")
|
@available(*, deprecated, message: "Use 'delete(sectionAt:)' method instead")
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func delete(index: Int) -> Self {
|
public func delete(index: Int) -> Self {
|
||||||
|
|
||||||
sections.remove(at: index)
|
sections.remove(at: index)
|
||||||
return self
|
return self
|
||||||
|
|
|
||||||
|
|
@ -24,17 +24,31 @@ struct TableKitNotifications {
|
||||||
static let CellAction = "TableKitNotificationsCellAction"
|
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 {
|
public protocol RowConfigurable {
|
||||||
|
|
||||||
func configure(_ cell: UITableViewCell)
|
func configure(_ cell: UITableViewCell)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol RowActionable {
|
public protocol RowActionable {
|
||||||
|
var leadingContextualActions: [UIContextualAction] { get }
|
||||||
var editingActions: [UITableViewRowAction]? { get }
|
var trailingContextualActions: [UIContextualAction] { get }
|
||||||
|
var performsFirstActionWithFullSwipe: Bool { get }
|
||||||
|
|
||||||
func isEditingAllowed(forIndexPath indexPath: IndexPath) -> Bool
|
func isEditingAllowed(forIndexPath indexPath: IndexPath) -> Bool
|
||||||
|
|
||||||
func invoke(action: TableRowActionType, cell: UITableViewCell?, path: IndexPath, userInfo: [AnyHashable: Any]?) -> Any?
|
func invoke(
|
||||||
|
action: TableRowActionType,
|
||||||
|
cell: UITableViewCell?,
|
||||||
|
path: IndexPath,
|
||||||
|
userInfo: [AnyHashable: Any]?) -> Any?
|
||||||
|
|
||||||
func has(action: TableRowActionType) -> Bool
|
func has(action: TableRowActionType) -> Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +61,8 @@ public protocol Row: RowConfigurable, RowActionable, RowHashable {
|
||||||
|
|
||||||
var reuseIdentifier: String { get }
|
var reuseIdentifier: String { get }
|
||||||
var cellType: AnyClass { get }
|
var cellType: AnyClass { get }
|
||||||
|
|
||||||
|
var layoutType: LayoutType { get }
|
||||||
var estimatedHeight: CGFloat? { get }
|
var estimatedHeight: CGFloat? { get }
|
||||||
var defaultHeight: CGFloat? { get }
|
var defaultHeight: CGFloat? { get }
|
||||||
}
|
}
|
||||||
|
|
@ -59,11 +74,21 @@ public enum TableRowActionType {
|
||||||
case select
|
case select
|
||||||
case deselect
|
case deselect
|
||||||
case willSelect
|
case willSelect
|
||||||
|
case willDeselect
|
||||||
case willDisplay
|
case willDisplay
|
||||||
|
case didEndDisplaying
|
||||||
case shouldHighlight
|
case shouldHighlight
|
||||||
|
case shouldBeginMultipleSelection
|
||||||
|
case didBeginMultipleSelection
|
||||||
case height
|
case height
|
||||||
case canEdit
|
case canEdit
|
||||||
case configure
|
case configure
|
||||||
|
case canDelete
|
||||||
|
case canMove
|
||||||
|
case canMoveTo
|
||||||
|
case move
|
||||||
|
case showContextMenu
|
||||||
|
case accessoryButtonTap
|
||||||
case custom(String)
|
case custom(String)
|
||||||
|
|
||||||
var key: String {
|
var key: String {
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,14 @@ open class TablePrototypeCellHeightCalculator: RowHeightCalculator {
|
||||||
|
|
||||||
guard let cell = prototypeCell else { return 0 }
|
guard let cell = prototypeCell else { return 0 }
|
||||||
|
|
||||||
|
cell.prepareForReuse()
|
||||||
row.configure(cell)
|
row.configure(cell)
|
||||||
|
|
||||||
cell.bounds = CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: cell.bounds.height)
|
cell.bounds = CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: cell.bounds.height)
|
||||||
cell.setNeedsLayout()
|
cell.setNeedsLayout()
|
||||||
cell.layoutIfNeeded()
|
cell.layoutIfNeeded()
|
||||||
|
|
||||||
let height = cell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height + (tableView.separatorStyle != .none ? separatorHeight : 0)
|
let height = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + (tableView.separatorStyle != .none ? separatorHeight : 0)
|
||||||
|
|
||||||
cachedHeights[hash] = height
|
cachedHeights[hash] = height
|
||||||
|
|
||||||
|
|
@ -77,7 +78,7 @@ open class TablePrototypeCellHeightCalculator: RowHeightCalculator {
|
||||||
return estimatedHeight
|
return estimatedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
return UITableViewAutomaticDimension
|
return UITableView.automaticDimension
|
||||||
}
|
}
|
||||||
|
|
||||||
open func invalidate() {
|
open func invalidate() {
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,21 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableViewCell {
|
open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableViewCell {
|
||||||
|
|
||||||
open let item: CellType.T
|
public let item: CellType.CellData
|
||||||
private lazy var actions = [String: [TableRowAction<CellType>]]()
|
private lazy var actions = [String: [TableRowAction<CellType>]]()
|
||||||
private(set) open var editingActions: [UITableViewRowAction]?
|
|
||||||
|
open var leadingContextualActions: [UIContextualAction] {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
|
||||||
|
open var trailingContextualActions: [UIContextualAction] {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
|
||||||
|
open var performsFirstActionWithFullSwipe: Bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
open var hashValue: Int {
|
open var hashValue: Int {
|
||||||
return ObjectIdentifier(self).hashValue
|
return ObjectIdentifier(self).hashValue
|
||||||
|
|
@ -41,15 +52,20 @@ open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableView
|
||||||
open var defaultHeight: CGFloat? {
|
open var defaultHeight: CGFloat? {
|
||||||
return CellType.defaultHeight
|
return CellType.defaultHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open var layoutType: LayoutType {
|
||||||
|
return CellType.layoutType
|
||||||
|
}
|
||||||
|
|
||||||
open var cellType: AnyClass {
|
open var cellType: AnyClass {
|
||||||
return CellType.self
|
return CellType.self
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(item: CellType.T, actions: [TableRowAction<CellType>]? = nil, editingActions: [UITableViewRowAction]? = nil) {
|
public init(item: CellType.CellData,
|
||||||
|
actions: [TableRowAction<CellType>]? = nil) {
|
||||||
|
|
||||||
self.item = item
|
self.item = item
|
||||||
self.editingActions = editingActions
|
|
||||||
actions?.forEach { on($0) }
|
actions?.forEach { on($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,12 +75,12 @@ open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableView
|
||||||
|
|
||||||
(cell as? CellType)?.configure(with: item)
|
(cell as? CellType)?.configure(with: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - RowActionable -
|
// MARK: - RowActionable -
|
||||||
|
|
||||||
open func invoke(action: TableRowActionType, cell: UITableViewCell?, path: IndexPath, userInfo: [AnyHashable: Any]? = nil) -> Any? {
|
open func invoke(action: TableRowActionType, cell: UITableViewCell?, path: IndexPath, userInfo: [AnyHashable: Any]? = nil) -> Any? {
|
||||||
|
|
||||||
return actions[action.key]?.flatMap({ $0.invokeActionOn(cell: cell, item: item, path: path, userInfo: userInfo) }).last
|
return actions[action.key]?.compactMap({ $0.invokeActionOn(cell: cell, item: item, path: path, userInfo: userInfo) }).last
|
||||||
}
|
}
|
||||||
|
|
||||||
open func has(action: TableRowActionType) -> Bool {
|
open func has(action: TableRowActionType) -> Bool {
|
||||||
|
|
@ -77,7 +93,10 @@ open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableView
|
||||||
if actions[TableRowActionType.canEdit.key] != nil {
|
if actions[TableRowActionType.canEdit.key] != nil {
|
||||||
return invoke(action: .canEdit, cell: nil, path: indexPath) as? Bool ?? false
|
return invoke(action: .canEdit, cell: nil, path: indexPath) as? Bool ?? false
|
||||||
}
|
}
|
||||||
return editingActions?.isEmpty == false || actions[TableRowActionType.clickDelete.key] != nil
|
|
||||||
|
return !leadingContextualActions.isEmpty
|
||||||
|
|| !trailingContextualActions.isEmpty
|
||||||
|
|| actions[TableRowActionType.clickDelete.key] != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - actions -
|
// MARK: - actions -
|
||||||
|
|
@ -113,7 +132,7 @@ open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableView
|
||||||
open func removeAction(forActionId actionId: String) {
|
open func removeAction(forActionId actionId: String) {
|
||||||
|
|
||||||
for (key, value) in actions {
|
for (key, value) in actions {
|
||||||
if let actionIndex = value.index(where: { $0.id == actionId }) {
|
if let actionIndex = value.firstIndex(where: { $0.id == actionId }) {
|
||||||
actions[key]?.remove(at: actionIndex)
|
actions[key]?.remove(at: actionIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@ import UIKit
|
||||||
|
|
||||||
open class TableRowActionOptions<CellType: ConfigurableCell> where CellType: UITableViewCell {
|
open class TableRowActionOptions<CellType: ConfigurableCell> where CellType: UITableViewCell {
|
||||||
|
|
||||||
open let item: CellType.T
|
public let item: CellType.CellData
|
||||||
open let cell: CellType?
|
public let cell: CellType?
|
||||||
open let indexPath: IndexPath
|
public let indexPath: IndexPath
|
||||||
open let userInfo: [AnyHashable: Any]?
|
public let userInfo: [AnyHashable: Any]?
|
||||||
|
|
||||||
init(item: CellType.T, cell: CellType?, path: IndexPath, userInfo: [AnyHashable: Any]?) {
|
init(item: CellType.CellData, cell: CellType?, path: IndexPath, userInfo: [AnyHashable: Any]?) {
|
||||||
|
|
||||||
self.item = item
|
self.item = item
|
||||||
self.cell = cell
|
self.cell = cell
|
||||||
|
|
@ -55,7 +55,7 @@ private enum TableRowActionHandler<CellType: ConfigurableCell> where CellType: U
|
||||||
open class TableRowAction<CellType: ConfigurableCell> where CellType: UITableViewCell {
|
open class TableRowAction<CellType: ConfigurableCell> where CellType: UITableViewCell {
|
||||||
|
|
||||||
open var id: String?
|
open var id: String?
|
||||||
open let type: TableRowActionType
|
public let type: TableRowActionType
|
||||||
private let handler: TableRowActionHandler<CellType>
|
private let handler: TableRowActionHandler<CellType>
|
||||||
|
|
||||||
public init(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> Void) {
|
public init(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> Void) {
|
||||||
|
|
@ -76,7 +76,7 @@ open class TableRowAction<CellType: ConfigurableCell> where CellType: UITableVie
|
||||||
self.handler = .action(handler)
|
self.handler = .action(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func invokeActionOn(cell: UITableViewCell?, item: CellType.T, path: IndexPath, userInfo: [AnyHashable: Any]?) -> Any? {
|
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))
|
return handler.invoke(withOptions: TableRowActionOptions(item: item, cell: cell as? CellType, path: path, userInfo: userInfo))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ open class TableSection {
|
||||||
|
|
||||||
open var headerTitle: String?
|
open var headerTitle: String?
|
||||||
open var footerTitle: String?
|
open var footerTitle: String?
|
||||||
|
open var indexTitle: String?
|
||||||
|
|
||||||
open var headerView: UIView?
|
open var headerView: UIView?
|
||||||
open var footerView: UIView?
|
open var footerView: UIView?
|
||||||
|
|
@ -88,6 +89,10 @@ open class TableSection {
|
||||||
rows[index] = row
|
rows[index] = row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func swap(from: Int, to: Int) {
|
||||||
|
rows.swapAt(from, to)
|
||||||
|
}
|
||||||
|
|
||||||
open func delete(rowAt index: Int) {
|
open func delete(rowAt index: Int) {
|
||||||
rows.remove(at: index)
|
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.name = 'TableKit'
|
||||||
s.module_name = 'TableKit'
|
s.module_name = 'TableKit'
|
||||||
|
|
||||||
s.version = '2.2.0'
|
s.version = '2.12'
|
||||||
|
|
||||||
s.homepage = 'https://github.com/maxsokolov/TableKit'
|
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/TableKit'
|
||||||
s.summary = 'Type-safe declarative table views with Swift.'
|
s.summary = 'Type-safe declarative table views with Swift.'
|
||||||
|
|
||||||
s.author = { 'Max Sokolov' => 'i@maxsokolov.net' }
|
s.author = { 'Max Sokolov' => 'i@maxsokolov.net' }
|
||||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||||
s.platforms = { :ios => '8.0' }
|
s.platforms = { :ios => '12.0' }
|
||||||
s.ios.deployment_target = '8.0'
|
s.ios.deployment_target = '12.0'
|
||||||
|
|
||||||
s.source_files = 'Sources/*.swift'
|
s.source_files = 'Sources/*.swift'
|
||||||
s.source = { :git => 'https://github.com/maxsokolov/TableKit.git', :tag => s.version }
|
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/TableKit.git', :tag => s.version }
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
2CBFA2F521F692F100147B56 /* ExpandableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBFA2F421F692F100147B56 /* ExpandableState.swift */; };
|
||||||
|
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */; };
|
||||||
|
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 */; };
|
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */; };
|
||||||
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E858571DB153F500A9AA55 /* TableKit.swift */; };
|
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E858571DB153F500A9AA55 /* TableKit.swift */; };
|
||||||
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */; };
|
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */; };
|
||||||
|
|
@ -32,6 +38,12 @@
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
2CBFA2F421F692F100147B56 /* ExpandableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableState.swift; sourceTree = "<group>"; };
|
||||||
|
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellHeightCalculator.swift; sourceTree = "<group>"; };
|
||||||
|
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>"; };
|
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>"; };
|
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; };
|
DA9EA7561D0B679A0021F650 /* TableKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TableKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
|
@ -90,16 +102,22 @@
|
||||||
DA9EA7A51D0EC2B90021F650 /* Sources */ = {
|
DA9EA7A51D0EC2B90021F650 /* Sources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
50E858571DB153F500A9AA55 /* TableKit.swift */,
|
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */,
|
||||||
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */,
|
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */,
|
||||||
|
DA9EA7A81D0EC2C90021F650 /* Operators.swift */,
|
||||||
|
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */,
|
||||||
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */,
|
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */,
|
||||||
|
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */,
|
||||||
|
50E858571DB153F500A9AA55 /* TableKit.swift */,
|
||||||
|
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */,
|
||||||
DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */,
|
DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */,
|
||||||
DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */,
|
DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */,
|
||||||
DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */,
|
DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */,
|
||||||
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */,
|
3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */,
|
||||||
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */,
|
3201E78721BE9EB2001DF9E7 /* Expandable.swift */,
|
||||||
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */,
|
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */,
|
||||||
DA9EA7A81D0EC2C90021F650 /* Operators.swift */,
|
32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */,
|
||||||
|
2CBFA2F421F692F100147B56 /* ExpandableState.swift */,
|
||||||
);
|
);
|
||||||
path = Sources;
|
path = Sources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -177,16 +195,16 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 0730;
|
LastSwiftUpdateCheck = 0730;
|
||||||
LastUpgradeCheck = 0800;
|
LastUpgradeCheck = 1000;
|
||||||
ORGANIZATIONNAME = "Max Sokolov";
|
ORGANIZATIONNAME = "Max Sokolov";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
DA9EA7551D0B679A0021F650 = {
|
DA9EA7551D0B679A0021F650 = {
|
||||||
CreatedOnToolsVersion = 7.3;
|
CreatedOnToolsVersion = 7.3;
|
||||||
LastSwiftMigration = 0800;
|
LastSwiftMigration = 1000;
|
||||||
};
|
};
|
||||||
DA9EA7C31D0EC45F0021F650 = {
|
DA9EA7C31D0EC45F0021F650 = {
|
||||||
CreatedOnToolsVersion = 7.3;
|
CreatedOnToolsVersion = 7.3;
|
||||||
LastSwiftMigration = 0800;
|
LastSwiftMigration = 1000;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -195,6 +213,7 @@
|
||||||
developmentRegion = English;
|
developmentRegion = English;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
|
English,
|
||||||
en,
|
en,
|
||||||
);
|
);
|
||||||
mainGroup = DA9EA74C1D0B679A0021F650;
|
mainGroup = DA9EA74C1D0B679A0021F650;
|
||||||
|
|
@ -230,13 +249,19 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
3201E78A21BE9ED4001DF9E7 /* ExpandableCellViewModel.swift in Sources */,
|
||||||
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */,
|
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */,
|
||||||
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */,
|
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */,
|
||||||
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */,
|
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */,
|
||||||
|
3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */,
|
||||||
|
2CBFA2F521F692F100147B56 /* ExpandableState.swift in Sources */,
|
||||||
DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */,
|
DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */,
|
||||||
DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */,
|
DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */,
|
||||||
|
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */,
|
||||||
DA9EA7B51D0EC2C90021F650 /* TableRowAction.swift in Sources */,
|
DA9EA7B51D0EC2C90021F650 /* TableRowAction.swift in Sources */,
|
||||||
DA9EA7B21D0EC2C90021F650 /* TableCellAction.swift in Sources */,
|
DA9EA7B21D0EC2C90021F650 /* TableCellAction.swift in Sources */,
|
||||||
|
32BDFE9F21C167F400D0BBB4 /* LayoutType.swift in Sources */,
|
||||||
|
3201E78621BE9E25001DF9E7 /* UITableViewCell+Extensions.swift in Sources */,
|
||||||
DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */,
|
DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */,
|
||||||
DA9EA7B41D0EC2C90021F650 /* TableRow.swift in Sources */,
|
DA9EA7B41D0EC2C90021F650 /* TableRow.swift in Sources */,
|
||||||
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */,
|
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */,
|
||||||
|
|
@ -271,14 +296,22 @@
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_INT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
|
@ -307,6 +340,7 @@
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
VERSION_INFO_PREFIX = "";
|
VERSION_INFO_PREFIX = "";
|
||||||
|
|
@ -322,14 +356,22 @@
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_INT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
|
@ -351,6 +393,7 @@
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
|
@ -375,7 +418,7 @@
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -395,7 +438,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKit;
|
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKit;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
@ -407,7 +450,7 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -419,7 +462,7 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0800"
|
LastUpgradeVersion = "1000"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ class TestTableViewCell: UITableViewCell, ConfigurableCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabletTests: XCTestCase {
|
class TableKitTests: XCTestCase {
|
||||||
|
|
||||||
var testController: TestController!
|
var testController: TestController!
|
||||||
|
|
||||||
|
|
@ -74,7 +74,10 @@ class TabletTests: XCTestCase {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
|
||||||
testController = TestController()
|
testController = TestController()
|
||||||
testController.view.isHidden = false
|
testController.tableView.frame = UIScreen.main.bounds
|
||||||
|
testController.tableView.isHidden = false
|
||||||
|
testController.tableView.setNeedsLayout()
|
||||||
|
testController.tableView.layoutIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDown() {
|
override func tearDown() {
|
||||||
|
|
@ -190,4 +193,53 @@ class TabletTests: XCTestCase {
|
||||||
|
|
||||||
waitForExpectations(timeout: 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