Compare commits

..

No commits in common. "master" and "1.0.0" have entirely different histories.

38 changed files with 452 additions and 1439 deletions

View File

@ -1 +0,0 @@
5.7

View File

@ -1,5 +1,5 @@
language: objective-c language: objective-c
osx_image: xcode11 osx_image: xcode7.3
branches: branches:
only: only:
- master - master
@ -7,18 +7,20 @@ 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=iphonesimulator13.0 - IOS_SDK=iphonesimulator9.3
- SCHEME_IOS="TableKit" - SCHEME_IOS="TableKit"
- PROJECT_FRAMEWORK="TableKit.xcodeproj" - PROJECT_FRAMEWORK="TableKit.xcodeproj"
matrix: matrix:
- DESTINATION="OS=10.3.1,name=iPhone 5" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK" - DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=11.1,name=iPhone 6" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK" - DESTINATION="OS=8.2,name=iPhone 5" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=12.0,name=iPhone 7 Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK" - DESTINATION="OS=9.0,name=iPhone 6" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=13.0,name=iPhone 11" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK" - DESTINATION="OS=9.1,name=iPhone 6 Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=9.2,name=iPhone 6S" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=9.3,name=iPhone 6S Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
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 CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO test - xcodebuild -project "$PROJECT_FRAMEWORK" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty -c;

View File

@ -1,55 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
## [2.10.0](https://github.com/maxsokolov/TableKit/releases/tag/2.10.0)
Released on 2019-09-29.
- Swift 5.1 support.
## [2.9.0](https://github.com/maxsokolov/TableKit/releases/tag/2.9.0)
Released on 2019-04-04.
- Swift 5.0 support.
## [2.8.0](https://github.com/maxsokolov/TableKit/releases/tag/2.8.0)
Released on 2018-09-30.
- Swift 4.2 support.
## [2.5.0](https://github.com/maxsokolov/TableKit/releases/tag/2.5.0)
Released on 2017-09-24.
- Swift 4.0 support.
## [2.3.0](https://github.com/maxsokolov/TableKit/releases/tag/2.3.0)
Released on 2016-11-16.
- `shouldUsePrototypeCellHeightCalculation` moved to `TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true)`
- Prototype cell height calculation bugfixes
## [2.1.0](https://github.com/maxsokolov/TableKit/releases/tag/2.1.0)
Released on 2016-10-19.
- `action` method was deprecated on TableRow. Use `on` instead.
- Support multiple actions with same type on row.
- You could now build your own cell height calculating strategy. See [TablePrototypeCellHeightCalculator](Sources/TablePrototypeCellHeightCalculator.swift).
- Default distance between sections changed to `UITableViewAutomaticDimension`. You can customize it, see [TableSection](Sources/TableSection.swift)
## [2.0.0](https://github.com/maxsokolov/TableKit/releases/tag/2.0.0)
Released on 2016-10-06. Breaking changes in 2.0.0:
<br/>The signatures of `TableRow` and `TableRowAction` classes were changed from
```swift
let action = TableRowAction<String, StringTableViewCell>(.click) { (data) in
}
let row = TableRow<String, StringTableViewCell>(item: "some string", actions: [action])
```
to
```swift
let action = TableRowAction<StringTableViewCell>(.click) { (data) in
}
let row = TableRow<StringTableViewCell>(item: "some string", actions: [action])
```
This is the great improvement that comes from the community. Thanks a lot!
## [1.3.0](https://github.com/maxsokolov/TableKit/releases/tag/1.3.0)
Released on 2016-09-04. Swift 3.0 support.
## [0.1.0](https://github.com/maxsokolov/TableKit/releases/tag/0.1.0)
Released on 2015-11-15. Initial release called Tablet.

View File

@ -1,7 +1,15 @@
//
// 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?
} }

View File

@ -1,3 +1,11 @@
//
// 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
@ -5,66 +13,26 @@ class AutolayoutCellsController: UIViewController {
@IBOutlet weak var tableView: UITableView! { @IBOutlet weak var tableView: UITableView! {
didSet { didSet {
tableDirector = TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true) tableDirector = TableDirector(tableView: tableView)
} }
} }
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 header = AutolayoutSectionHeaderView.loadFromNib() let section = TableSection()
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 <= 20 { while rows <= 10 {
rows += 1 rows += 1
let row = TableRow<AutolayoutTableViewCell>(item: randomString(length: randomInt(min: 20, max: 100))) let row = TableRow<Void, AutolayoutTableViewCell>(item: ())
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
}
}

View File

@ -1,3 +1,11 @@
//
// 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
@ -15,30 +23,25 @@ class MainController: UIViewController {
title = "TableKit" title = "TableKit"
let clickAction = TableRowAction<ConfigurableTableViewCell>(.click) { [weak self] (options) in let clickAction = TableRowAction<String, ConfigurableTableViewCell>(.click) { [weak self] (data) in
switch options.indexPath.row { switch data.indexPath.row {
case 0: case 0:
self?.performSegue(withIdentifier: "autolayoutcells", sender: nil) self?.performSegueWithIdentifier("autolayoutcells", sender: nil)
case 1: case 1:
self?.performSegue(withIdentifier: "nibcells", sender: nil) self?.performSegueWithIdentifier("nibcells", sender: nil)
default: default:
break break
} }
} }
let printClickAction = TableRowAction<ConfigurableTableViewCell>(.click) { (options) in let rows: [Row] = [
print("click", options.indexPath)
}
let rows = [ TableRow<String, ConfigurableTableViewCell>(item: "Autolayout cells", actions: [clickAction]),
TableRow<String, ConfigurableTableViewCell>(item: "Nib cells", actions: [clickAction])
TableRow<ConfigurableTableViewCell>(item: "Autolayout cells", actions: [clickAction, printClickAction]),
TableRow<ConfigurableTableViewCell>(item: "Nib cells", actions: [clickAction, printClickAction])
] ]
// automatically creates a section, also could be used like tableDirector.append(rows: rows) // automatically creates a section, also could be used like tableDirector.append(rows: rows)
tableDirector += rows tableDirector += rows
} }
} }

View File

@ -1,3 +1,11 @@
//
// 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
@ -13,14 +21,13 @@ class NibCellsController: UITableViewController {
tableDirector = TableDirector(tableView: tableView) tableDirector = TableDirector(tableView: tableView)
let numbers = [1000, 2000, 3000, 4000, 5000] let numbers = [1000, 2000, 3000, 4000, 5000]
let rows = numbers.map { let shouldHighlightAction = TableRowAction<Int, NibTableViewCell>(.shouldHighlight) { (_) -> Bool in
TableRow<NibTableViewCell>(item: $0) return false
.on(.shouldHighlight) { (_) -> Bool in
return false
}
} }
let rows: [Row] = numbers.map { TableRow<Int, NibTableViewCell>(item: $0, actions: [shouldHighlightAction]) }
tableDirector.append(rows: rows) tableDirector.append(rows: rows)
} }
} }

View File

@ -1,24 +0,0 @@
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
}
}

View File

@ -1,39 +0,0 @@
<?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>

View File

@ -1,31 +1,31 @@
//
// 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 = String typealias T = Void
@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 150 return 500
} }
func configure(with string: T) { func configure(with string: T) {
titleLabel.text = LoremIpsumTitle titleLabel.text = LoremIpsumTitle
subtitleLabel.text = string subtitleLabel.text = LoremIpsumBody
} }
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.layoutIfNeeded()
titleLabel.preferredMaxLayoutWidth = titleLabel.bounds.size.width
subtitleLabel.preferredMaxLayoutWidth = subtitleLabel.bounds.size.width
}
}

View File

@ -1,3 +1,11 @@
//
// 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
@ -5,7 +13,7 @@ class ConfigurableTableViewCell: UITableViewCell, ConfigurableCell {
func configure(with text: String) { func configure(with text: String) {
accessoryType = .disclosureIndicator accessoryType = .DisclosureIndicator
textLabel?.text = text textLabel?.text = text
} }
} }

View File

@ -1,3 +1,11 @@
//
// 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
@ -12,4 +20,4 @@ class NibTableViewCell: UITableViewCell, ConfigurableCell {
func configure(with number: Int) { func configure(with number: Int) {
titleLabel.text = "\(number)" titleLabel.text = "\(number)"
} }
} }

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10117" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10116" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
@ -11,21 +11,19 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/> <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4JI-V9-Bra" id="Bal-V1-EZ2"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4JI-V9-Bra" id="Bal-V1-EZ2">
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/> <rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qBe-iP-Q1p"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qBe-iP-Q1p">
<rect key="frame" x="8" y="11" width="304" height="21"/> <rect key="frame" x="8" y="11" width="45" height="21"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="qBe-iP-Q1p" firstAttribute="top" secondItem="Bal-V1-EZ2" secondAttribute="top" constant="11" id="B84-GR-YoJ"/> <constraint firstItem="qBe-iP-Q1p" firstAttribute="centerY" secondItem="Bal-V1-EZ2" secondAttribute="centerY" id="8gn-1I-szV"/>
<constraint firstAttribute="bottom" secondItem="qBe-iP-Q1p" secondAttribute="bottom" constant="12" id="Cmh-Tj-2Pw"/> <constraint firstItem="qBe-iP-Q1p" firstAttribute="leading" secondItem="Bal-V1-EZ2" secondAttribute="leading" constant="8" id="xMK-TB-Bis"/>
<constraint firstAttribute="trailing" secondItem="qBe-iP-Q1p" secondAttribute="trailing" constant="8" id="ZJP-9s-4ZM"/>
<constraint firstItem="qBe-iP-Q1p" firstAttribute="leading" secondItem="Bal-V1-EZ2" secondAttribute="leading" constant="8" id="wAW-KV-dCJ"/>
</constraints> </constraints>
</tableViewCellContentView> </tableViewCellContentView>
<connections> <connections>

View File

@ -7,8 +7,6 @@
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 */; };
@ -63,8 +61,6 @@
/* 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>"; };
@ -198,8 +194,6 @@
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>";
@ -233,13 +227,12 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0720; LastSwiftUpdateCheck = 0720;
LastUpgradeCheck = 1000; LastUpgradeCheck = 0700;
ORGANIZATIONNAME = Tablet; ORGANIZATIONNAME = Tablet;
TargetAttributes = { TargetAttributes = {
DAB7EB261BEF787300D2AD5E = { DAB7EB261BEF787300D2AD5E = {
CreatedOnToolsVersion = 7.0.1; CreatedOnToolsVersion = 7.0.1;
DevelopmentTeam = Z48R734SJX; DevelopmentTeam = Z48R734SJX;
LastSwiftMigration = 1000;
}; };
}; };
}; };
@ -248,7 +241,6 @@
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
Base, Base,
); );
@ -291,7 +283,6 @@
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 */,
@ -309,7 +300,6 @@
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 */,
@ -335,23 +325,13 @@
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_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_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@ -378,7 +358,6 @@
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;
}; };
@ -390,23 +369,13 @@
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_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_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@ -425,8 +394,6 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 5.0;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;
@ -434,34 +401,32 @@
DAB7EB3A1BEF787300D2AD5E /* Debug */ = { DAB7EB3A1BEF787300D2AD5E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
INFOPLIST_FILE = Resources/Info.plist; INFOPLIST_FILE = Resources/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo; PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
PRODUCT_NAME = TableKitDemo; PRODUCT_NAME = TableKitDemo;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
DAB7EB3B1BEF787300D2AD5E /* Release */ = { DAB7EB3B1BEF787300D2AD5E /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
INFOPLIST_FILE = Resources/Info.plist; INFOPLIST_FILE = Resources/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo; PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.demo;
PRODUCT_NAME = TableKitDemo; PRODUCT_NAME = TableKitDemo;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };

View File

@ -1,8 +0,0 @@
<?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>

View File

@ -1,19 +1,5 @@
// 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")
]
)

132
README.md
View File

@ -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_5.1-compatible-4BC51D.svg?style=flat" alt="Swift 5.1 compatible" /></a> <a href="https://developer.apple.com/swift"><img src="https://img.shields.io/badge/Swift_2.2-compatible-4BC51D.svg?style=flat" alt="Swift 2.2 compatible" /></a>
<a href="https://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.11.0-blue.svg" alt="CocoaPods compatible" /></a> <a href="https://cocoapods.org/pods/tablekit"><img src="https://img.shields.io/badge/pod-1.0.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>
@ -36,9 +36,9 @@ Create your rows:
```swift ```swift
import TableKit import TableKit
let row1 = TableRow<StringTableViewCell>(item: "1") let row1 = TableRow<String, StringTableViewCell>(item: "1")
let row2 = TableRow<IntTableViewCell>(item: 2) let row2 = TableRow<Int, IntTableViewCell>(item: 2)
let row3 = TableRow<UserTableViewCell>(item: User(name: "John Doe", rating: 5)) let row3 = TableRow<User, UserTableViewCell>(item: User(name: "John Doe", rating: 5))
``` ```
Put rows into section: Put rows into section:
```swift ```swift
@ -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"
} }
// is not required to be implemented func configure(with user: User) {
// 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.
@ -84,27 +84,26 @@ You could have as many rows and sections as you need.
It nice to have some actions that related to your cells: It nice to have some actions that related to your cells:
```swift ```swift
let action = TableRowAction<StringTableViewCell>(.click) { (options) in let action = TableRowAction<String, StringTableViewCell>(.click) { (data) 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? // data.cell - StringTableViewCell?
// options.item - String // data.item - String
// options.indexPath - IndexPath // data.indexPath - NSIndexPath
// options.userInfo - [AnyHashable: Any]?
} }
let row = TableRow<StringTableViewCell>(item: "some", actions: [action]) let row = TableRow<String, 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<String, StringTableViewCell>(item: "some")
.on(.click) { (options) in .action(.click) { (data) in
} }
.on(.shouldHighlight) { (options) -> Bool in .action(.shouldHighlight) { (data) -> 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,37 +118,18 @@ 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:
```swift ```swift
let myAction = TableRowAction<MyTableViewCell>(.custom(MyActions.ButtonClicked)) { (options) in let myAction = TableRowAction<Void, MyTableViewCell>(.custom(MyActions.ButtonClicked)) { (data) in
} }
``` ```
## Multiple actions with same type
It's also possible to use multiple actions with same type:
```swift
let click1 = TableRowAction<StringTableViewCell>(.click) { (options) in }
click1.id = "click1" // optional
let click2 = TableRowAction<StringTableViewCell>(.click) { (options) in }
click2.id = "click2" // optional
let row = TableRow<StringTableViewCell>(item: "some", actions: [click1, click2])
```
Could be useful in case if you want to separate your logic somehow. Actions will be invoked in order which they were attached.
> If you define multiple actions with same type which also return a value, only last return value will be used for table view.
You could also remove any action by id:
```swift
row.removeAction(forActionId: "action_id")
```
# Advanced # Advanced
@ -158,31 +138,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
let tableDirector = TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true) tableDirector.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/HeightStrategy.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
} }
} }
``` ```
@ -193,18 +173,18 @@ It's never been so easy to deal with table views.
```swift ```swift
let users = /* some users array */ let users = /* some users array */
let click = TableRowAction<UserTableViewCell>(.click) { let click = TableRowAction<String, UserTableViewCell>(.click) {
} }
let rows = users.filter({ $0.state == .active }).map({ TableRow<UserTableViewCell>(item: $0.name, actions: [click]) }) let rows: [Row] = users.filter({ $0.state == .active }).map({ TableRow<String, UserTableViewCell>(item: $0.name, actions: [click]) })
tableDirector += rows 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 matches cell's xib name: TableKit can register your cells in table view automatically. In case if your reusable cell id mathces cell's xib name:
```ruby ```ruby
MyTableViewCell.swift MyTableViewCell.swift
@ -232,13 +212,9 @@ Clone the repo and drag files from `Sources` folder into your Xcode project.
# Requirements # Requirements
- iOS 8.0 - iOS 8.0+
- Xcode 9.0 - Xcode 7.0+
# Changelog
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.

View File

@ -22,36 +22,34 @@ import UIKit
public protocol ConfigurableCell { public protocol ConfigurableCell {
associatedtype CellData associatedtype T
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 }
static var layoutType: LayoutType { get } func configure(with _: T)
func configure(with _: CellData)
} }
public extension ConfigurableCell where Self: UITableViewCell { public extension ConfigurableCell where Self: UITableViewCell {
static var reuseIdentifier: String { static var reuseIdentifier: String {
return String(describing: self) get {
return String(self)
}
} }
static var estimatedHeight: CGFloat? { static var estimatedHeight: CGFloat? {
return nil get {
return UITableViewAutomaticDimension
}
} }
static var defaultHeight: CGFloat? { static var defaultHeight: CGFloat? {
return nil get {
return nil
}
} }
}
static var layoutType: LayoutType {
return .auto
}
}

View File

@ -1,96 +0,0 @@
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)
}
}
}

View File

@ -1,55 +0,0 @@
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()
}
}

View File

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

View File

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

View File

@ -20,18 +20,26 @@
import UIKit import UIKit
open class TablePrototypeCellHeightCalculator: RowHeightCalculator { public protocol CellHeightCalculatable {
private(set) weak var tableView: UITableView? func height(row: Row, path: NSIndexPath) -> CGFloat
func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat
func invalidate()
}
public class PrototypeHeightStrategy: CellHeightCalculatable {
private weak var tableView: UITableView?
private var prototypes = [String: UITableViewCell]() private var prototypes = [String: UITableViewCell]()
private var cachedHeights = [Int: CGFloat]() private var cachedHeights = [Int: CGFloat]()
private var separatorHeight = 1 / UIScreen.main.scale private var separatorHeight = 1 / UIScreen.mainScreen().scale
public init(tableView: UITableView?) { init(tableView: UITableView?) {
self.tableView = tableView self.tableView = tableView
} }
open func height(forRow row: Row, at indexPath: IndexPath) -> CGFloat { public func height(row: Row, path: NSIndexPath) -> CGFloat {
guard let tableView = tableView else { return 0 } guard let tableView = tableView else { return 0 }
@ -41,47 +49,27 @@ open class TablePrototypeCellHeightCalculator: RowHeightCalculator {
return height return height
} }
var prototypeCell = prototypes[row.reuseIdentifier] guard let cell = tableView.dequeueReusableCellWithIdentifier(row.reuseIdentifier) else { return 0 }
if prototypeCell == nil {
prototypeCell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier) cell.bounds = CGRectMake(0, 0, tableView.bounds.size.width, cell.bounds.height)
prototypes[row.reuseIdentifier] = prototypeCell
}
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.setNeedsLayout() cell.setNeedsLayout()
cell.layoutIfNeeded() cell.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + (tableView.separatorStyle != .none ? separatorHeight : 0) let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (tableView.separatorStyle != .None ? separatorHeight : 0)
cachedHeights[hash] = height cachedHeights[hash] = height
return height return height
} }
open func estimatedHeight(forRow row: Row, at indexPath: IndexPath) -> CGFloat { public func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
guard let tableView = tableView else { return 0 }
let hash = row.hashValue ^ Int(tableView.bounds.size.width).hashValue
if let height = cachedHeights[hash] {
return height
}
if let estimatedHeight = row.estimatedHeight , estimatedHeight > 0 {
return estimatedHeight
}
return UITableView.automaticDimension
} }
open func invalidate() { public func invalidate() {
cachedHeights.removeAll() cachedHeights.removeAll()
} }
} }

View File

@ -1,7 +0,0 @@
public enum LayoutType {
case manual
case auto
}

View File

@ -43,4 +43,4 @@ public func +=(left: TableSection, right: Row) {
public func +=(left: TableSection, right: [Row]) { public func +=(left: TableSection, right: [Row]) {
left.append(rows: right) left.append(rows: right)
} }

View File

@ -20,11 +20,15 @@
import UIKit import UIKit
struct TableKitNotifications {
static let CellAction = "TableKitNotificationsCellAction"
}
/** /**
A custom action that you can trigger from your cell. A custom action that you can trigger from your cell.
You can easily catch actions using a chaining manner with your row. You can eacily catch actions using a chaining manner with your row builder.
*/ */
open class TableCellAction { public class TableCellAction {
/// The cell that triggers an action. /// The cell that triggers an action.
public let cell: UITableViewCell public let cell: UITableViewCell
@ -33,16 +37,16 @@ open class TableCellAction {
public let key: String public let key: String
/// The custom user info. /// The custom user info.
public let userInfo: [AnyHashable: Any]? public let userInfo: [NSObject: AnyObject]?
public init(key: String, sender: UITableViewCell, userInfo: [AnyHashable: Any]? = nil) { public init(key: String, sender: UITableViewCell, userInfo: [NSObject: AnyObject]? = nil) {
self.key = key self.key = key
self.cell = sender self.cell = sender
self.userInfo = userInfo self.userInfo = userInfo
} }
open func invoke() { public func invoke() {
NotificationCenter.default.post(name: Notification.Name(rawValue: TableKitNotifications.CellAction), object: self, userInfo: userInfo) NSNotificationCenter.defaultCenter().postNotificationName(TableKitNotifications.CellAction, object: self, userInfo: userInfo)
} }
} }

View File

@ -20,8 +20,8 @@
import UIKit import UIKit
class TableCellRegisterer { class TableCellManager {
private var registeredIds = Set<String>() private var registeredIds = Set<String>()
private weak var tableView: UITableView? private weak var tableView: UITableView?
@ -29,30 +29,30 @@ class TableCellRegisterer {
self.tableView = tableView self.tableView = tableView
} }
func register(cellType: AnyClass, forCellReuseIdentifier reuseIdentifier: String) { func register(cellType cellType: AnyClass, forCellReuseIdentifier reuseIdentifier: String) {
if registeredIds.contains(reuseIdentifier) { if registeredIds.contains(reuseIdentifier) {
return return
} }
// check if cell is already registered, probably cell has been registered by storyboard // check if cell is already registered, probably cell has been registered by storyboard
if tableView?.dequeueReusableCell(withIdentifier: reuseIdentifier) != nil { if tableView?.dequeueReusableCellWithIdentifier(reuseIdentifier) != nil {
registeredIds.insert(reuseIdentifier) registeredIds.insert(reuseIdentifier)
return return
} }
let bundle = Bundle(for: cellType) let bundle = NSBundle(forClass: cellType)
// we hope that cell's xib file has name that equals to cell's class name // we hope that cell's xib file has name that equals to cell's class name
// 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.pathForResource(reuseIdentifier, ofType: "nib") {
tableView?.register(UINib(nibName: reuseIdentifier, bundle: bundle), forCellReuseIdentifier: reuseIdentifier) tableView?.registerNib(UINib(nibName: reuseIdentifier, bundle: bundle), forCellReuseIdentifier: reuseIdentifier)
// otherwise, register cell class // otherwise, register cell class
} else { } else {
tableView?.register(cellType, forCellReuseIdentifier: reuseIdentifier) tableView?.registerClass(cellType, forCellReuseIdentifier: reuseIdentifier)
} }
registeredIds.insert(reuseIdentifier) registeredIds.insert(reuseIdentifier)
} }
} }

View File

@ -23,177 +23,112 @@ import UIKit
/** /**
Responsible for table view's datasource and delegate. Responsible for table view's datasource and delegate.
*/ */
open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate { public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
open private(set) weak var tableView: UITableView? public private(set) weak var tableView: UITableView?
open fileprivate(set) var sections = [TableSection]() public private(set) var sections = [TableSection]()
private weak var scrollDelegate: UIScrollViewDelegate? private weak var scrollDelegate: UIScrollViewDelegate?
private var cellRegisterer: TableCellRegisterer? private var heightStrategy: CellHeightCalculatable?
public private(set) var rowHeightCalculator: RowHeightCalculator? private var cellManager: TableCellManager?
private var sectionsIndexTitlesIndexes: [Int]?
@available(*, deprecated, message: "Produced incorrect behaviour") public var shouldUsePrototypeCellHeightCalculation: Bool = false {
open var shouldUsePrototypeCellHeightCalculation: Bool = false {
didSet { didSet {
if shouldUsePrototypeCellHeightCalculation { if shouldUsePrototypeCellHeightCalculation {
rowHeightCalculator = TablePrototypeCellHeightCalculator(tableView: tableView) heightStrategy = PrototypeHeightStrategy(tableView: tableView)
} else {
rowHeightCalculator = nil
} }
} }
} }
open var isEmpty: Bool { public var isEmpty: Bool {
return sections.isEmpty return sections.isEmpty
} }
public init( public init(tableView: UITableView, scrollDelegate: UIScrollViewDelegate? = nil, shouldUseAutomaticCellRegistration: Bool = true) {
tableView: UITableView,
scrollDelegate: UIScrollViewDelegate? = nil,
shouldUseAutomaticCellRegistration: Bool = true,
cellHeightCalculator: RowHeightCalculator?)
{
super.init() super.init()
if shouldUseAutomaticCellRegistration { if shouldUseAutomaticCellRegistration {
self.cellRegisterer = TableCellRegisterer(tableView: tableView) self.cellManager = TableCellManager(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)
}
public convenience init(
tableView: UITableView,
scrollDelegate: UIScrollViewDelegate? = nil,
shouldUseAutomaticCellRegistration: Bool = true,
shouldUsePrototypeCellHeightCalculation: Bool = false)
{
let heightCalculator: TablePrototypeCellHeightCalculator? = shouldUsePrototypeCellHeightCalculation
? TablePrototypeCellHeightCalculator(tableView: tableView)
: nil
self.init( NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(didReceiveAction), name: TableKitNotifications.CellAction, object: nil)
tableView: tableView,
scrollDelegate: scrollDelegate,
shouldUseAutomaticCellRegistration: shouldUseAutomaticCellRegistration,
cellHeightCalculator: heightCalculator
)
} }
deinit { deinit {
NotificationCenter.default.removeObserver(self) NSNotificationCenter.defaultCenter().removeObserver(self)
} }
open func reload() { public func reload() {
tableView?.reloadData() tableView?.reloadData()
} }
// MARK: - Private
private func row(at indexPath: IndexPath) -> Row? {
if indexPath.section < sections.count && indexPath.row < sections[indexPath.section].rows.count {
return sections[indexPath.section].rows[indexPath.row]
}
return nil
}
// MARK: Public // MARK: Public
@discardableResult
open func invoke( public func invoke(action action: TableRowActionType, cell: UITableViewCell?, indexPath: NSIndexPath) -> Any? {
action: TableRowActionType, return sections[indexPath.section].items[indexPath.row].invoke(action, cell: cell, path: indexPath)
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 { public override func respondsToSelector(selector: Selector) -> Bool {
return super.responds(to: selector) || scrollDelegate?.responds(to: selector) == true return super.respondsToSelector(selector) || scrollDelegate?.respondsToSelector(selector) == true
} }
open override func forwardingTarget(for selector: Selector) -> Any? { public override func forwardingTargetForSelector(selector: Selector) -> AnyObject? {
return scrollDelegate?.responds(to: selector) == true return scrollDelegate?.respondsToSelector(selector) == true ? scrollDelegate : super.forwardingTargetForSelector(selector)
? scrollDelegate
: super.forwardingTarget(for: selector)
} }
// MARK: - Internal // MARK: - Internal -
func hasAction(_ action: TableRowActionType, atIndexPath indexPath: IndexPath) -> Bool {
guard let row = row(at: indexPath) else { return false } func hasAction(action: TableRowActionType, atIndexPath indexPath: NSIndexPath) -> Bool {
return row.has(action: action) return sections[indexPath.section].items[indexPath.row].hasAction(action)
} }
@objc func didReceiveAction(notification: NSNotification) {
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, indexPath = tableView?.indexPathForCell(action.cell) else { return }
invoke(action: .custom(action.key), cell: action.cell, indexPath: indexPath, userInfo: notification.userInfo) invoke(action: .custom(action.key), cell: action.cell, indexPath: indexPath)
} }
// MARK: - Height // MARK: - Height
open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let row = sections[indexPath.section].rows[indexPath.row]
if rowHeightCalculator != nil { let row = sections[indexPath.section].items[indexPath.row]
cellRegisterer?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier) return row.estimatedHeight ?? heightStrategy?.estimatedHeight(row, path: indexPath) ?? UITableViewAutomaticDimension
}
return row.defaultHeight
?? row.estimatedHeight
?? rowHeightCalculator?.estimatedHeight(forRow: row, at: indexPath)
?? UITableView.automaticDimension
} }
open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let row = sections[indexPath.section].rows[indexPath.row] let row = sections[indexPath.section].items[indexPath.row]
if rowHeightCalculator != nil {
cellRegisterer?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier)
}
let rowHeight = invoke(action: .height, cell: nil, indexPath: indexPath) as? CGFloat let rowHeight = invoke(action: .height, cell: nil, indexPath: indexPath) as? CGFloat
return rowHeight return rowHeight ?? row.defaultHeight ?? heightStrategy?.height(row, path: indexPath) ?? UITableViewAutomaticDimension
?? row.defaultHeight
?? rowHeightCalculator?.height(forRow: row, at: indexPath)
?? UITableView.automaticDimension
} }
// MARK: UITableViewDataSource - configuration // MARK: UITableViewDataSource - configuration
open func numberOfSections(in tableView: UITableView) -> Int {
public func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count return sections.count
} }
open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard section < sections.count else { return 0 }
return sections[section].numberOfRows return sections[section].numberOfRows
} }
open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = sections[indexPath.section].rows[indexPath.row] let row = sections[indexPath.section].items[indexPath.row]
cellRegisterer?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier) cellManager?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier)
let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier, for: indexPath) let cell = tableView.dequeueReusableCellWithIdentifier(row.reuseIdentifier, forIndexPath: indexPath)
if cell.frame.size.width != tableView.frame.size.width { if cell.frame.size.width != tableView.frame.size.width {
cell.frame = CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: cell.frame.size.height) cell.frame = CGRectMake(0, 0, tableView.frame.size.width, cell.frame.size.height)
cell.layoutIfNeeded() cell.layoutIfNeeded()
} }
@ -204,272 +139,106 @@ open class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
} }
// MARK: UITableViewDataSource - section setup // MARK: UITableViewDataSource - section setup
open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard section < sections.count else { return nil } public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].headerTitle return sections[section].headerTitle
} }
open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { public 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? {
guard section < sections.count else { return nil } public func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return sections[section].headerView return sections[section].headerView
} }
open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { public 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 { public 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 ?? UITableView.automaticDimension return section.headerHeight ?? section.headerView?.frame.size.height ?? 0
} }
open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { public 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 return section.footerHeight ?? section.footerView?.frame.size.height ?? 0
?? 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) {
let cell = tableView.cellForRow(at: indexPath) public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
if invoke(action: .click, cell: cell, indexPath: indexPath) != nil { if invoke(action: .click, cell: cell, indexPath: indexPath) != nil {
tableView.deselectRow(at: indexPath, animated: true) tableView.deselectRowAtIndexPath(indexPath, animated: true)
} else { } else {
invoke(action: .select, cell: cell, indexPath: indexPath) invoke(action: .select, cell: cell, indexPath: indexPath)
} }
} }
open func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { public func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
invoke(action: .deselect, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) invoke(action: .deselect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath)
} }
open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
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) { public func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
invoke(action: .didEndDisplaying, cell: cell, indexPath: indexPath) return invoke(action: .shouldHighlight, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? true
} }
open func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { public func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
return invoke(action: .shouldHighlight, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? true
}
open func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if hasAction(.willSelect, atIndexPath: indexPath) { if hasAction(.willSelect, atIndexPath: indexPath) {
return invoke(action: .willSelect, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? IndexPath return invoke(action: .willSelect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? NSIndexPath
} }
return indexPath return indexPath
} }
open func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? {
if hasAction(.willDeselect, atIndexPath: indexPath) {
return invoke(action: .willDeselect, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? IndexPath
}
return indexPath
}
@available(iOS 13.0, *)
open func tableView(
_ tableView: UITableView,
shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool
{
invoke(action: .shouldBeginMultipleSelection, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? false
}
@available(iOS 13.0, *)
open func tableView(
_ tableView: UITableView,
didBeginMultipleSelectionInteractionAt indexPath: IndexPath)
{
invoke(action: .didBeginMultipleSelection, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath)
}
@available(iOS 13.0, *) // MARK: - Sections manipulation -
open func tableView(
_ tableView: UITableView,
contextMenuConfigurationForRowAt indexPath: IndexPath,
point: CGPoint) -> UIContextMenuConfiguration?
{
invoke(action: .showContextMenu, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath, userInfo: [TableKitUserInfoKeys.ContextMenuInvokePoint: point]) as? UIContextMenuConfiguration
}
// MARK: - Row editing
open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return sections[indexPath.section].rows[indexPath.row].isEditingAllowed(forIndexPath: indexPath)
}
open func tableView(_ tableView: UITableView,
leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let currentRow = sections[indexPath.section].rows[indexPath.row]
let configuration = UISwipeActionsConfiguration(actions: currentRow.leadingContextualActions)
configuration.performsFirstActionWithFullSwipe = currentRow.performsFirstActionWithFullSwipe
return configuration
}
open func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let currentRow = sections[indexPath.section].rows[indexPath.row]
let configuration = UISwipeActionsConfiguration(actions: currentRow.trailingContextualActions)
configuration.performsFirstActionWithFullSwipe = currentRow.performsFirstActionWithFullSwipe
return configuration
}
open func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { public func append(section section: TableSection) -> Self {
if invoke(action: .canDelete, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? false {
return UITableViewCell.EditingStyle.delete
}
return UITableViewCell.EditingStyle.none
}
public func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
public func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
return invoke(action: .canMoveTo, cell: tableView.cellForRow(at: sourceIndexPath), indexPath: sourceIndexPath, userInfo: [TableKitUserInfoKeys.CellCanMoveProposedIndexPath: proposedDestinationIndexPath]) as? IndexPath ?? proposedDestinationIndexPath
}
open func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
invoke(action: .clickDelete, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath)
}
}
open func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return invoke(action: .canMove, cell: tableView.cellForRow(at: indexPath), indexPath: indexPath) as? Bool ?? false
}
open func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
invoke(action: .move, cell: tableView.cellForRow(at: sourceIndexPath), indexPath: sourceIndexPath, userInfo: [TableKitUserInfoKeys.CellMoveDestinationIndexPath: destinationIndexPath])
}
open func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
invoke(action: .accessoryButtonTap, cell: cell, indexPath: indexPath)
}
}
// MARK: - Sections manipulation
extension TableDirector {
@discardableResult
public func append(section: TableSection) -> Self {
append(sections: [section]) append(sections: [section])
return self return self
} }
@discardableResult public func append(sections sections: [TableSection]) -> Self {
public func append(sections: [TableSection]) -> Self {
sections.forEach { $0.tableDirector = self }
self.sections.append(contentsOf: sections) self.sections.appendContentsOf(sections)
return self return self
} }
@discardableResult public func append(rows rows: [Row]) -> Self {
public func append(rows: [Row]) -> Self {
append(section: TableSection(rows: rows)) append(section: TableSection(rows: rows))
return self return self
} }
@discardableResult public func insert(section section: TableSection, atIndex index: Int) -> Self {
public func insert(section: TableSection, atIndex index: Int) -> Self {
sections.insert(section, atIndex: index)
sections.insert(section, at: index)
return self return self
} }
@discardableResult public func delete(index index: Int) -> Self {
public func replaceSection(at index: Int, with section: TableSection) -> Self {
sections.removeAtIndex(index)
if index < sections.count {
sections[index] = section
}
return self return self
} }
@discardableResult
public func delete(sectionAt index: Int) -> Self {
sections.remove(at: index)
return self
}
@discardableResult
public func remove(sectionAt index: Int) -> Self {
return delete(sectionAt: index)
}
@discardableResult
public func clear() -> Self { public func clear() -> Self {
rowHeightCalculator?.invalidate()
sections.removeAll() sections.removeAll()
return self return self
} }
}
// MARK: - deprecated methods
@available(*, deprecated, message: "Use 'delete(sectionAt:)' method instead")
@discardableResult
public func delete(index: Int) -> Self {
sections.remove(at: index)
return self
}
}

View File

@ -1,111 +0,0 @@
//
// Copyright (c) 2015 Max Sokolov https://twitter.com/max_sokolov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import UIKit
struct TableKitNotifications {
static let CellAction = "TableKitNotificationsCellAction"
}
public struct TableKitUserInfoKeys {
public static let CellMoveDestinationIndexPath = "TableKitCellMoveDestinationIndexPath"
public static let CellCanMoveProposedIndexPath = "CellCanMoveProposedIndexPath"
public static let ContextMenuInvokePoint = "ContextMenuInvokePoint"
}
public protocol RowConfigurable {
func configure(_ cell: UITableViewCell)
}
public protocol RowActionable {
var leadingContextualActions: [UIContextualAction] { get }
var trailingContextualActions: [UIContextualAction] { get }
var performsFirstActionWithFullSwipe: Bool { get }
func isEditingAllowed(forIndexPath indexPath: IndexPath) -> Bool
func invoke(
action: TableRowActionType,
cell: UITableViewCell?,
path: IndexPath,
userInfo: [AnyHashable: Any]?) -> Any?
func has(action: TableRowActionType) -> Bool
}
public protocol RowHashable {
var hashValue: Int { get }
}
public protocol Row: RowConfigurable, RowActionable, RowHashable {
var reuseIdentifier: String { get }
var cellType: AnyClass { get }
var layoutType: LayoutType { get }
var estimatedHeight: CGFloat? { get }
var defaultHeight: CGFloat? { get }
}
public enum TableRowActionType {
case click
case clickDelete
case select
case deselect
case willSelect
case willDeselect
case willDisplay
case didEndDisplaying
case shouldHighlight
case shouldBeginMultipleSelection
case didBeginMultipleSelection
case height
case canEdit
case configure
case canDelete
case canMove
case canMoveTo
case move
case showContextMenu
case accessoryButtonTap
case custom(String)
var key: String {
switch (self) {
case .custom(let key):
return key
default:
return "_\(self)"
}
}
}
public protocol RowHeightCalculator {
func height(forRow row: Row, at indexPath: IndexPath) -> CGFloat
func estimatedHeight(forRow row: Row, at indexPath: IndexPath) -> CGFloat
func invalidate()
}

View File

@ -20,137 +20,89 @@
import UIKit import UIKit
open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableViewCell { public protocol RowConfigurable {
public let item: CellType.CellData
private lazy var actions = [String: [TableRowAction<CellType>]]()
open var leadingContextualActions: [UIContextualAction] {
[]
}
open var trailingContextualActions: [UIContextualAction] {
[]
}
open var performsFirstActionWithFullSwipe: Bool {
false
}
open var hashValue: Int { func configure(cell: UITableViewCell)
}
public protocol RowActionable {
func invoke(action: TableRowActionType, cell: UITableViewCell?, path: NSIndexPath) -> Any?
func hasAction(action: TableRowActionType) -> Bool
}
public protocol RowHashable {
var hashValue: Int { get }
}
public protocol Row: RowConfigurable, RowActionable, RowHashable {
var reuseIdentifier: String { get }
var cellType: AnyClass { get }
var estimatedHeight: CGFloat? { get }
var defaultHeight: CGFloat? { get }
}
public class TableRow<ItemType, CellType: ConfigurableCell where CellType.T == ItemType, CellType: UITableViewCell>: Row {
public let item: ItemType
private lazy var actions = [String: TableRowAction<ItemType, CellType>]()
public var hashValue: Int {
return ObjectIdentifier(self).hashValue return ObjectIdentifier(self).hashValue
} }
open var reuseIdentifier: String { public var reuseIdentifier: String {
return CellType.reuseIdentifier return CellType.reuseIdentifier
} }
open var estimatedHeight: CGFloat? { public var estimatedHeight: CGFloat? {
return CellType.estimatedHeight return CellType.estimatedHeight
} }
open var defaultHeight: CGFloat? { public var defaultHeight: CGFloat? {
return CellType.defaultHeight return CellType.defaultHeight
} }
open var layoutType: LayoutType {
return CellType.layoutType
}
open var cellType: AnyClass { public var cellType: AnyClass {
return CellType.self return CellType.self
} }
public init(item: CellType.CellData, public init(item: ItemType, actions: [TableRowAction<ItemType, CellType>]? = nil) {
actions: [TableRowAction<CellType>]? = nil) {
self.item = item self.item = item
actions?.forEach { self.actions[$0.type.key] = $0 }
actions?.forEach { on($0) }
} }
// MARK: - RowConfigurable - // MARK: - RowConfigurable -
open func configure(_ cell: UITableViewCell) { public func configure(cell: UITableViewCell) {
(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? { public func invoke(action: TableRowActionType, cell: UITableViewCell?, path: NSIndexPath) -> Any? {
return actions[action.key]?.invoke(item: item, cell: cell, path: path)
return actions[action.key]?.compactMap({ $0.invokeActionOn(cell: cell, item: item, path: path, userInfo: userInfo) }).last
} }
open func has(action: TableRowActionType) -> Bool { public func hasAction(action: TableRowActionType) -> Bool {
return actions[action.key] != nil return actions[action.key] != nil
} }
open func isEditingAllowed(forIndexPath indexPath: IndexPath) -> Bool {
if actions[TableRowActionType.canEdit.key] != nil {
return invoke(action: .canEdit, cell: nil, path: indexPath) as? Bool ?? false
}
return !leadingContextualActions.isEmpty
|| !trailingContextualActions.isEmpty
|| actions[TableRowActionType.clickDelete.key] != nil
}
// MARK: - actions - // MARK: - actions -
@discardableResult public func action(action: TableRowAction<ItemType, CellType>) -> Self {
open func on(_ action: TableRowAction<CellType>) -> Self {
if actions[action.type.key] == nil {
actions[action.type.key] = [TableRowAction<CellType>]()
}
actions[action.type.key]?.append(action)
actions[action.type.key] = action
return self return self
} }
@discardableResult public func action<T>(type: TableRowActionType, handler: (data: TableRowActionData<ItemType, CellType>) -> T) -> Self {
open func on<T>(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> T) -> Self {
return on(TableRowAction<CellType>(type, handler: handler)) actions[type.key] = TableRowAction(type, handler: handler)
return self
} }
}
@discardableResult
open func on(_ key: String, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> ()) -> Self {
return on(TableRowAction<CellType>(.custom(key), handler: handler))
}
open func removeAllActions() {
actions.removeAll()
}
open func removeAction(forActionId actionId: String) {
for (key, value) in actions {
if let actionIndex = value.firstIndex(where: { $0.id == actionId }) {
actions[key]?.remove(at: actionIndex)
}
}
}
// MARK: - deprecated actions -
@available(*, deprecated, message: "Use 'on' method instead")
@discardableResult
open func action(_ action: TableRowAction<CellType>) -> Self {
return on(action)
}
@available(*, deprecated, message: "Use 'on' method instead")
@discardableResult
open func action<T>(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> T) -> Self {
return on(TableRowAction<CellType>(type, handler: handler))
}
}

View File

@ -20,14 +20,37 @@
import UIKit import UIKit
open class TableRowActionOptions<CellType: ConfigurableCell> where CellType: UITableViewCell { public enum TableRowActionType {
case click
case select
case deselect
case willSelect
case willDisplay
case shouldHighlight
case height
case configure
case custom(String)
var key: String {
switch (self) {
case .custom(let key):
return key
default:
return "_\(self)"
}
}
}
public let item: CellType.CellData public class TableRowActionData<ItemType, CellType: ConfigurableCell where CellType.T == ItemType, CellType: UITableViewCell> {
public let item: ItemType
public let cell: CellType? public let cell: CellType?
public let indexPath: IndexPath public let indexPath: NSIndexPath
public let userInfo: [AnyHashable: Any]? public let userInfo: [NSObject: AnyObject]?
init(item: CellType.CellData, cell: CellType?, path: IndexPath, userInfo: [AnyHashable: Any]?) { init(item: ItemType, cell: CellType?, path: NSIndexPath, userInfo: [NSObject: AnyObject]?) {
self.item = item self.item = item
self.cell = cell self.cell = cell
@ -36,48 +59,24 @@ open class TableRowActionOptions<CellType: ConfigurableCell> where CellType: UIT
} }
} }
private enum TableRowActionHandler<CellType: ConfigurableCell> where CellType: UITableViewCell { public class TableRowAction<ItemType, CellType: ConfigurableCell where CellType.T == ItemType, CellType: UITableViewCell> {
case voidAction((TableRowActionOptions<CellType>) -> Void)
case action((TableRowActionOptions<CellType>) -> Any?)
func invoke(withOptions options: TableRowActionOptions<CellType>) -> Any? {
switch self {
case .voidAction(let handler):
return handler(options)
case .action(let handler):
return handler(options)
}
}
}
open class TableRowAction<CellType: ConfigurableCell> where CellType: UITableViewCell {
open var id: String?
public let type: TableRowActionType public let type: TableRowActionType
private let handler: TableRowActionHandler<CellType> private let handler: ((data: TableRowActionData<ItemType, CellType>) -> Any?)
public init(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> Void) { public init(_ type: TableRowActionType, handler: (data: TableRowActionData<ItemType, CellType>) -> Void) {
self.type = type self.type = type
self.handler = .voidAction(handler) self.handler = handler
} }
public init(_ key: String, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> Void) { public init<T>(_ type: TableRowActionType, handler: (data: TableRowActionData<ItemType, CellType>) -> T) {
self.type = .custom(key)
self.handler = .voidAction(handler)
}
public init<T>(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> T) {
self.type = type self.type = type
self.handler = .action(handler) self.handler = handler
} }
public func invokeActionOn(cell: UITableViewCell?, item: CellType.CellData, path: IndexPath, userInfo: [AnyHashable: Any]?) -> Any? { func invoke(item item: ItemType, cell: UITableViewCell?, path: NSIndexPath) -> Any? {
return handler(data: TableRowActionData(item: item, cell: cell as? CellType, path: path, userInfo: nil))
return handler.invoke(withOptions: TableRowActionOptions(item: item, cell: cell as? CellType, path: path, userInfo: userInfo))
} }
} }

View File

@ -20,32 +20,33 @@
import UIKit import UIKit
open class TableSection { public class TableSection {
open private(set) var rows = [Row]()
open var headerTitle: String? weak var tableDirector: TableDirector?
open var footerTitle: String?
open var indexTitle: String?
open var headerView: UIView? public private(set) var items = [Row]()
open var footerView: UIView?
open var headerHeight: CGFloat? = nil public var headerTitle: String?
open var footerHeight: CGFloat? = nil public var footerTitle: String?
open var numberOfRows: Int { public var headerView: UIView?
return rows.count public var footerView: UIView?
public var headerHeight: CGFloat? = nil
public var footerHeight: CGFloat? = nil
public var numberOfRows: Int {
return items.count
} }
open var isEmpty: Bool { public var isEmpty: Bool {
return rows.isEmpty return items.isEmpty
} }
public init(rows: [Row]? = nil) { public init(rows: [Row]? = nil) {
if let initialRows = rows { if let initialRows = rows {
self.rows.append(contentsOf: initialRows) items.appendContentsOf(initialRows)
} }
} }
@ -62,49 +63,26 @@ open class TableSection {
self.headerView = headerView self.headerView = headerView
self.footerView = footerView self.footerView = footerView
} }
// MARK: - Public - // MARK: - Public -
open func clear() { public func clear() {
rows.removeAll() items.removeAll()
} }
open func append(row: Row) { public func append(row row: Row) {
append(rows: [row]) append(rows: [row])
} }
open func append(rows: [Row]) { public func append(rows rows: [Row]) {
self.rows.append(contentsOf: rows) items.appendContentsOf(rows)
} }
open func insert(row: Row, at index: Int) { public func insert(row row: Row, atIndex index: Int) {
rows.insert(row, at: index) items.insert(row, atIndex: index)
} }
open func insert(rows: [Row], at index: Int) { public func delete(index index: Int) {
self.rows.insert(contentsOf: rows, at: index) items.removeAtIndex(index)
} }
}
open func replace(rowAt index: Int, with row: Row) {
rows[index] = row
}
open func swap(from: Int, to: Int) {
rows.swapAt(from, to)
}
open func delete(rowAt index: Int) {
rows.remove(at: index)
}
open func remove(rowAt index: Int) {
rows.remove(at: index)
}
// MARK: - deprecated methods -
@available(*, deprecated, message: "Use 'delete(rowAt:)' method instead")
open func delete(index: Int) {
rows.remove(at: index)
}
}

View File

@ -1,32 +0,0 @@
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
}
}
}

View File

@ -2,16 +2,16 @@ Pod::Spec.new do |s|
s.name = 'TableKit' s.name = 'TableKit'
s.module_name = 'TableKit' s.module_name = 'TableKit'
s.version = '2.12' s.version = '1.0.0'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/TableKit' s.homepage = 'https://github.com/maxsokolov/TableKit'
s.summary = 'Type-safe declarative table views with Swift.' s.summary = 'Type-safe declarative table views. Swift 2.2 is required.'
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 => '12.0' } s.platforms = { :ios => '8.0' }
s.ios.deployment_target = '12.0' s.ios.deployment_target = '8.0'
s.source_files = 'Sources/*.swift' s.source_files = 'Sources/*.swift'
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/TableKit.git', :tag => s.version } s.source = { :git => 'https://github.com/maxsokolov/TableKit.git', :tag => s.version }
end end

View File

@ -7,16 +7,9 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
2CBFA2F521F692F100147B56 /* ExpandableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBFA2F421F692F100147B56 /* ExpandableState.swift */; }; 50CF6E6B1D6704FE004746FF /* TableCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF6E6A1D6704FE004746FF /* TableCellManager.swift */; };
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */; };
3201E78621BE9E25001DF9E7 /* UITableViewCell+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */; };
3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78721BE9EB2001DF9E7 /* Expandable.swift */; };
3201E78A21BE9ED4001DF9E7 /* ExpandableCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */; };
32BDFE9F21C167F400D0BBB4 /* LayoutType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */; };
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */; };
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E858571DB153F500A9AA55 /* TableKit.swift */; };
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */; }; DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */; };
DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */; }; DA9EA7B01D0EC2C90021F650 /* HeightStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A71D0EC2C90021F650 /* HeightStrategy.swift */; };
DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A81D0EC2C90021F650 /* Operators.swift */; }; DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A81D0EC2C90021F650 /* Operators.swift */; };
DA9EA7B21D0EC2C90021F650 /* TableCellAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */; }; DA9EA7B21D0EC2C90021F650 /* TableCellAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */; };
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */; }; DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */; };
@ -38,17 +31,10 @@
/* 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>"; }; 50CF6E6A1D6704FE004746FF /* TableCellManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellManager.swift; sourceTree = "<group>"; };
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellHeightCalculator.swift; sourceTree = "<group>"; };
3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+Extensions.swift"; sourceTree = "<group>"; };
3201E78721BE9EB2001DF9E7 /* Expandable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expandable.swift; sourceTree = "<group>"; };
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellViewModel.swift; sourceTree = "<group>"; };
32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutType.swift; sourceTree = "<group>"; };
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellRegisterer.swift; sourceTree = "<group>"; };
50E858571DB153F500A9AA55 /* TableKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableKit.swift; sourceTree = "<group>"; };
DA9EA7561D0B679A0021F650 /* TableKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TableKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DA9EA7561D0B679A0021F650 /* TableKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TableKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = "<group>"; }; DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = "<group>"; };
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TablePrototypeCellHeightCalculator.swift; sourceTree = "<group>"; }; DA9EA7A71D0EC2C90021F650 /* HeightStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeightStrategy.swift; sourceTree = "<group>"; };
DA9EA7A81D0EC2C90021F650 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; }; DA9EA7A81D0EC2C90021F650 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellAction.swift; sourceTree = "<group>"; }; DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellAction.swift; sourceTree = "<group>"; };
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableDirector.swift; sourceTree = "<group>"; }; DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableDirector.swift; sourceTree = "<group>"; };
@ -102,22 +88,15 @@
DA9EA7A51D0EC2B90021F650 /* Sources */ = { DA9EA7A51D0EC2B90021F650 /* Sources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */,
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */,
DA9EA7A81D0EC2C90021F650 /* Operators.swift */,
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */,
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */,
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */, DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */,
50E858571DB153F500A9AA55 /* TableKit.swift */, 50CF6E6A1D6704FE004746FF /* TableCellManager.swift */,
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */,
DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */, DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */,
DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */, DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */,
DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */, DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */,
3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */, DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */,
3201E78721BE9EB2001DF9E7 /* Expandable.swift */, DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */,
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */, DA9EA7A81D0EC2C90021F650 /* Operators.swift */,
32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */, DA9EA7A71D0EC2C90021F650 /* HeightStrategy.swift */,
2CBFA2F421F692F100147B56 /* ExpandableState.swift */,
); );
path = Sources; path = Sources;
sourceTree = "<group>"; sourceTree = "<group>";
@ -195,16 +174,14 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0730; LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 1000; LastUpgradeCheck = 0730;
ORGANIZATIONNAME = "Max Sokolov"; ORGANIZATIONNAME = "Max Sokolov";
TargetAttributes = { TargetAttributes = {
DA9EA7551D0B679A0021F650 = { DA9EA7551D0B679A0021F650 = {
CreatedOnToolsVersion = 7.3; CreatedOnToolsVersion = 7.3;
LastSwiftMigration = 1000;
}; };
DA9EA7C31D0EC45F0021F650 = { DA9EA7C31D0EC45F0021F650 = {
CreatedOnToolsVersion = 7.3; CreatedOnToolsVersion = 7.3;
LastSwiftMigration = 1000;
}; };
}; };
}; };
@ -213,7 +190,6 @@
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
); );
mainGroup = DA9EA74C1D0B679A0021F650; mainGroup = DA9EA74C1D0B679A0021F650;
@ -249,22 +225,15 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
3201E78A21BE9ED4001DF9E7 /* ExpandableCellViewModel.swift in Sources */, 50CF6E6B1D6704FE004746FF /* TableCellManager.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 /* HeightStrategy.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 */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -296,23 +265,13 @@
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_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_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@ -340,7 +299,6 @@
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 = "";
@ -356,23 +314,13 @@
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_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_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@ -392,8 +340,6 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
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";
@ -405,7 +351,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1; DYLIB_CURRENT_VERSION = 1;
@ -418,7 +363,6 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -426,7 +370,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1; DYLIB_CURRENT_VERSION = 1;
@ -438,7 +381,6 @@
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 = 5.0;
}; };
name = Release; name = Release;
}; };
@ -450,7 +392,6 @@
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 = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -462,7 +403,6 @@
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 = 5.0;
}; };
name = Release; name = Release;
}; };

View File

@ -1,8 +0,0 @@
<?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>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1000" LastUpgradeVersion = "0730"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -66,7 +66,7 @@ class TestTableViewCell: UITableViewCell, ConfigurableCell {
} }
} }
class TableKitTests: XCTestCase { class TabletTests: XCTestCase {
var testController: TestController! var testController: TestController!
@ -74,10 +74,7 @@ class TableKitTests: XCTestCase {
super.setUp() super.setUp()
testController = TestController() testController = TestController()
testController.tableView.frame = UIScreen.main.bounds testController.view.hidden = false
testController.tableView.isHidden = false
testController.tableView.setNeedsLayout()
testController.tableView.layoutIfNeeded()
} }
override func tearDown() { override func tearDown() {
@ -97,15 +94,15 @@ class TableKitTests: XCTestCase {
let data = TestData(title: "title") let data = TestData(title: "title")
let row = TableRow<TestTableViewCell>(item: data) let row = TableRow<TestData, TestTableViewCell>(item: data)
testController.tableDirector += row testController.tableDirector += row
testController.tableView.reloadData() testController.tableView.reloadData()
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section") XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section") XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section")
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell
XCTAssertNotNil(cell) XCTAssertNotNil(cell)
XCTAssertTrue(cell?.textLabel?.text == data.title) XCTAssertTrue(cell?.textLabel?.text == data.title)
} }
@ -114,17 +111,17 @@ class TableKitTests: XCTestCase {
let data = [TestData(title: "1"), TestData(title: "2"), TestData(title: "3")] let data = [TestData(title: "1"), TestData(title: "2"), TestData(title: "3")]
let rows: [Row] = data.map({ TableRow<TestTableViewCell>(item: $0) }) let rows: [Row] = data.map({ TableRow<TestData, TestTableViewCell>(item: $0) })
testController.tableDirector += rows testController.tableDirector += rows
testController.tableView.reloadData() testController.tableView.reloadData()
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section") XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == data.count, "Table view should have certain number of rows in a section") XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == data.count, "Table view should have certain number of rows in a section")
for (index, element) in data.enumerated() { for (index, element) in data.enumerate() {
let cell = testController.tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? TestTableViewCell let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TestTableViewCell
XCTAssertNotNil(cell) XCTAssertNotNil(cell)
XCTAssertTrue(cell?.textLabel?.text == element.title) XCTAssertTrue(cell?.textLabel?.text == element.title)
} }
@ -132,7 +129,7 @@ class TableKitTests: XCTestCase {
func testTableSectionCreatesSectionWithHeaderAndFooterTitles() { func testTableSectionCreatesSectionWithHeaderAndFooterTitles() {
let row = TableRow<TestTableViewCell>(item: TestData(title: "title")) let row = TableRow<TestData, TestTableViewCell>(item: TestData(title: "title"))
let sectionHeaderTitle = "Header Title" let sectionHeaderTitle = "Header Title"
let sectionFooterTitle = "Footer Title" let sectionFooterTitle = "Footer Title"
@ -142,7 +139,7 @@ class TableKitTests: XCTestCase {
testController.tableDirector += section testController.tableDirector += section
testController.tableView.reloadData() testController.tableView.reloadData()
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section") XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section") XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView?(testController.tableView, titleForHeaderInSection: 0) == sectionHeaderTitle) XCTAssertTrue(testController.tableView.dataSource?.tableView?(testController.tableView, titleForHeaderInSection: 0) == sectionHeaderTitle)
@ -151,7 +148,7 @@ class TableKitTests: XCTestCase {
func testTableSectionCreatesSectionWithHeaderAndFooterViews() { func testTableSectionCreatesSectionWithHeaderAndFooterViews() {
let row = TableRow<TestTableViewCell>(item: TestData(title: "title")) let row = TableRow<TestData, TestTableViewCell>(item: TestData(title: "title"))
let sectionHeaderView = UIView() let sectionHeaderView = UIView()
let sectionFooterView = UIView() let sectionFooterView = UIView()
@ -162,7 +159,7 @@ class TableKitTests: XCTestCase {
testController.tableDirector += section testController.tableDirector += section
testController.tableView.reloadData() testController.tableView.reloadData()
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section") XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section") XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section")
XCTAssertTrue(testController.tableView.delegate?.tableView?(testController.tableView, viewForHeaderInSection: 0) == sectionHeaderView) XCTAssertTrue(testController.tableView.delegate?.tableView?(testController.tableView, viewForHeaderInSection: 0) == sectionHeaderView)
@ -171,75 +168,26 @@ class TableKitTests: XCTestCase {
func testRowBuilderCustomActionInvokedAndSentUserInfo() { func testRowBuilderCustomActionInvokedAndSentUserInfo() {
let expectation = self.expectation(description: "cell action") let expectation = expectationWithDescription("cell action")
let row = TableRow<TestTableViewCell>(item: TestData(title: "title")) let row = TableRow<TestData, TestTableViewCell>(item: TestData(title: "title"))
.on(TableRowAction(.custom(TestTableViewCellOptions.CellAction)) { (data) in .action(TableRowAction(.custom(TestTableViewCellOptions.CellAction)) { (data) in
XCTAssertNotNil(data.cell, "Action data should have a cell") XCTAssertNotNil(data.cell, "Action data should have a cell")
expectation.fulfill() expectation.fulfill()
}) })
testController.view.isHidden = false testController.view.hidden = false
testController.tableDirector += row testController.tableDirector += row
testController.tableView.reloadData() testController.tableView.reloadData()
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell
XCTAssertNotNil(cell, "Cell should exists and should be TestTableViewCell") XCTAssertNotNil(cell, "Cell should exists and should be TestTableViewCell")
cell?.raiseAction() cell?.raiseAction()
waitForExpectations(timeout: 1.0, handler: nil) waitForExpectationsWithTimeout(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")
}
}