Compare commits

..

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

59 changed files with 1484 additions and 3003 deletions

View File

@ -1 +0,0 @@
5.7

View File

@ -1,24 +1,11 @@
language: objective-c language: objective-c
osx_image: xcode11 osx_image: xcode7.3
branches: branches:
only: only:
- master - master
env: before_install:
global: - brew update
- LC_CTYPE=en_US.UTF-8 - brew reinstall --HEAD xctool
- LANG=en_US.UTF-8 - cd Tablet
- IOS_SDK=iphonesimulator13.0
- SCHEME_IOS="TableKit"
- PROJECT_FRAMEWORK="TableKit.xcodeproj"
matrix:
- DESTINATION="OS=10.3.1,name=iPhone 5" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=11.1,name=iPhone 6" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=12.0,name=iPhone 7 Plus" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
- DESTINATION="OS=13.0,name=iPhone 11" SCHEME="$SCHEME_IOS" SDK="$IOS_SDK"
script: script:
- set -o pipefail - xctool clean build test -project TableKit.xcodeproj -scheme TableKit -sdk iphonesimulator
- xcodebuild -version
- xcodebuild -showsdks
- xcodebuild -project "$PROJECT_FRAMEWORK" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO test

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 +0,0 @@
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
}

View File

@ -1,70 +0,0 @@
import UIKit
import TableKit
class AutolayoutCellsController: UIViewController {
@IBOutlet weak var tableView: UITableView! {
didSet {
tableDirector = TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true)
}
}
var tableDirector: TableDirector!
func randomString(length: Int) -> String {
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let len = UInt32(letters.length)
var randomString = ""
for _ in 0 ..< length {
let rand = arc4random_uniform(len)
var nextChar = letters.character(at: Int(rand))
randomString += NSString(characters: &nextChar, length: 1) as String
}
return randomString
}
func randomInt(min: Int, max:Int) -> Int {
return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Autolayout cells"
let header = AutolayoutSectionHeaderView.loadFromNib()
let section = TableSection(headerView: header, footerView: nil)
section.headerHeight = getViewHeight(view: header, width: UIScreen.main.bounds.width)
var rows = 0
while rows <= 20 {
rows += 1
let row = TableRow<AutolayoutTableViewCell>(item: randomString(length: randomInt(min: 20, max: 100)))
section += row
}
tableDirector += section
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Clear", style: .done, target: self, action: #selector(clear))
}
@objc
func clear() {
tableDirector.clear()
tableDirector.reload()
}
func getViewHeight(view: UIView, width: CGFloat) -> CGFloat {
view.frame = CGRect(x: 0, y: 0, width: width, height: view.frame.size.height)
view.setNeedsLayout()
view.layoutIfNeeded()
return view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
}
}

View File

@ -1,44 +0,0 @@
import UIKit
import TableKit
class MainController: UIViewController {
@IBOutlet weak var tableView: UITableView! {
didSet {
tableDirector = TableDirector(tableView: tableView)
}
}
var tableDirector: TableDirector!
override func viewDidLoad() {
super.viewDidLoad()
title = "TableKit"
let clickAction = TableRowAction<ConfigurableTableViewCell>(.click) { [weak self] (options) in
switch options.indexPath.row {
case 0:
self?.performSegue(withIdentifier: "autolayoutcells", sender: nil)
case 1:
self?.performSegue(withIdentifier: "nibcells", sender: nil)
default:
break
}
}
let printClickAction = TableRowAction<ConfigurableTableViewCell>(.click) { (options) in
print("click", options.indexPath)
}
let rows = [
TableRow<ConfigurableTableViewCell>(item: "Autolayout cells", actions: [clickAction, printClickAction]),
TableRow<ConfigurableTableViewCell>(item: "Nib cells", actions: [clickAction, printClickAction])
]
// automatically creates a section, also could be used like tableDirector.append(rows: rows)
tableDirector += rows
}
}

View File

@ -1,26 +0,0 @@
import UIKit
import TableKit
class NibCellsController: UITableViewController {
var tableDirector: TableDirector!
override func viewDidLoad() {
super.viewDidLoad()
title = "Nib cells"
tableDirector = TableDirector(tableView: tableView)
let numbers = [1000, 2000, 3000, 4000, 5000]
let rows = numbers.map {
TableRow<NibTableViewCell>(item: $0)
.on(.shouldHighlight) { (_) -> Bool in
return false
}
}
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 +0,0 @@
import UIKit
import TableKit
private let LoremIpsumTitle = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"
class AutolayoutTableViewCell: UITableViewCell, ConfigurableCell {
typealias T = String
@IBOutlet var titleLabel: UILabel!
@IBOutlet var subtitleLabel: UILabel!
static var estimatedHeight: CGFloat? {
return 150
}
func configure(with string: T) {
titleLabel.text = LoremIpsumTitle
subtitleLabel.text = string
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.layoutIfNeeded()
titleLabel.preferredMaxLayoutWidth = titleLabel.bounds.size.width
subtitleLabel.preferredMaxLayoutWidth = subtitleLabel.bounds.size.width
}
}

View File

@ -1,11 +0,0 @@
import UIKit
import TableKit
class ConfigurableTableViewCell: UITableViewCell, ConfigurableCell {
func configure(with text: String) {
accessoryType = .disclosureIndicator
textLabel?.text = text
}
}

View File

@ -1,15 +0,0 @@
import UIKit
import TableKit
class NibTableViewCell: UITableViewCell, ConfigurableCell {
@IBOutlet weak var titleLabel: UILabel!
static var defaultHeight: CGFloat? {
return 100
}
func configure(with number: Int) {
titleLabel.text = "\(number)"
}
}

View File

@ -1,37 +0,0 @@
<?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">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="NibTableViewCell" id="4JI-V9-Bra" customClass="NibTableViewCell" customModule="TableKitDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4JI-V9-Bra" id="Bal-V1-EZ2">
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qBe-iP-Q1p">
<rect key="frame" x="8" y="11" width="304" height="21"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="qBe-iP-Q1p" firstAttribute="top" secondItem="Bal-V1-EZ2" secondAttribute="top" constant="11" id="B84-GR-YoJ"/>
<constraint firstAttribute="bottom" secondItem="qBe-iP-Q1p" secondAttribute="bottom" constant="12" id="Cmh-Tj-2Pw"/>
<constraint firstAttribute="trailing" secondItem="qBe-iP-Q1p" secondAttribute="trailing" constant="8" id="ZJP-9s-4ZM"/>
<constraint firstItem="qBe-iP-Q1p" firstAttribute="leading" secondItem="Bal-V1-EZ2" secondAttribute="leading" constant="8" id="wAW-KV-dCJ"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="titleLabel" destination="qBe-iP-Q1p" id="UO4-lc-Kr0"/>
</connections>
<point key="canvasLocation" x="524" y="319"/>
</tableViewCell>
</objects>
</document>

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 +0,0 @@
// swift-tools-version:5.7
import PackageDescription
let package = Package(
name: "TableKit",
products: [
.library(
name: "TableKit",
targets: ["TableKit"]),
],
targets: [
.target(
name: "TableKit",
path: "Sources")
]
)

243
README.md
View File

@ -1,244 +1,29 @@
# 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> <img src="https://img.shields.io/badge/platform-iOS-blue.svg?style=flat" alt="Platform iOS" />
<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-0.5.0-blue.svg" alt="CocoaPods compatible" /></a>
<img src="https://img.shields.io/badge/platform-iOS-blue.svg?style=flat" alt="Platform iOS" /> <a href="https://raw.githubusercontent.com/maxsokolov/tablekit/master/LICENSE"><img src="http://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="License: MIT" /></a>
<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>
TableKit is a super lightweight yet powerful generic library that allows you to build complex table views in a declarative type-safe manner. TableKit is a super lightweight yet powerful generic library that handles a complexity of UITableView's datasource and delegate methods in a Swift environment. Tablet's goal is to provide an easiest way to create complex table views. With Tablet you don't have to write a messy code of `switch` or `if` statements when you deal with bunch of different cells in different sections.
It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` methods behind the scene, so your code will be look clean, easy to read and nice to maintain.
# Features ## Features
- [x] Type-safe generic cells - [x] Type-safe cells based on generics
- [x] Functional programming style friendly
- [x] The easiest way to map your models or view models to cells - [x] The easiest way to map your models or view models to cells
- [x] Automatic cell registration*
- [x] Correctly handles autolayout cells with multiline labels - [x] Correctly handles autolayout cells with multiline labels
- [x] Chainable cell actions (select/deselect etc.) - [x] Chainable cell actions (select/deselect etc.)
- [x] Support cells created from code, xib, or storyboard - [x] Support cells created from code, xib, or storyboard
- [x] Support different cells height calculation strategies - [x] Automatic xib/classes registration
- [x] Support portrait and landscape orientations
- [x] No need to subclass - [x] No need to subclass
- [x] Extensibility - [x] Extensibility
- [x] Tests
# Getting Started Docs will be updated soon.
An [example app](Demo) is included demonstrating TableKit's functionality. ## License
## Basic usage Tablet is available under the MIT license. See LICENSE for details.
Create your rows:
```swift
import TableKit
let row1 = TableRow<StringTableViewCell>(item: "1")
let row2 = TableRow<IntTableViewCell>(item: 2)
let row3 = TableRow<UserTableViewCell>(item: User(name: "John Doe", rating: 5))
```
Put rows into section:
```swift
let section = TableSection(rows: [row1, row2, row3])
```
And setup your table:
```swift
let tableDirector = TableDirector(tableView: tableView)
tableDirector += section
```
Done. Your table is ready. Your cells have to conform to `ConfigurableCell` protocol:
```swift
class StringTableViewCell: UITableViewCell, ConfigurableCell {
func configure(with string: String) {
textLabel?.text = string
}
}
class UserTableViewCell: UITableViewCell, ConfigurableCell {
static var estimatedHeight: CGFloat? {
return 100
}
// is not required to be implemented
// by default reuse id is equal to cell's class name
static var reuseIdentifier: String {
return "my id"
}
func configure(with user: User) {
textLabel?.text = user.name
detailTextLabel?.text = "Rating: \(user.rating)"
}
}
```
You could have as many rows and sections as you need.
## Row actions
It nice to have some actions that related to your cells:
```swift
let action = TableRowAction<StringTableViewCell>(.click) { (options) in
// you could access any useful information that relates to the action
// options.cell - StringTableViewCell?
// options.item - String
// options.indexPath - IndexPath
// options.userInfo - [AnyHashable: Any]?
}
let row = TableRow<StringTableViewCell>(item: "some", actions: [action])
```
Or, using nice chaining approach:
```swift
let row = TableRow<StringTableViewCell>(item: "some")
.on(.click) { (options) in
}
.on(.shouldHighlight) { (options) -> Bool in
return false
}
```
You could find all available actions [here](Sources/TableRowAction.swift).
## Custom row actions
You are able to define your own actions:
```swift
struct MyActions {
static let ButtonClicked = "ButtonClicked"
}
class MyTableViewCell: UITableViewCell, ConfigurableCell {
@IBAction func myButtonClicked(sender: UIButton) {
TableCellAction(key: MyActions.ButtonClicked, sender: self).invoke()
}
}
```
And handle them accordingly:
```swift
let myAction = TableRowAction<MyTableViewCell>(.custom(MyActions.ButtonClicked)) { (options) 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
## Cell height calculating strategy
By default TableKit relies on <a href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html" target="_blank">self-sizing cells</a>. In that case you have to provide an estimated height for your cells:
```swift
class StringTableViewCell: UITableViewCell, ConfigurableCell {
// ...
static var estimatedHeight: CGFloat? {
return 255
}
}
```
It's enough for most cases. But you may be not happy with this. So you could use a prototype cell to calculate cells heights. To enable this feature simply use this property:
```swift
let tableDirector = TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true)
```
It does all dirty work with prototypes for you [behind the scene](Sources/TablePrototypeCellHeightCalculator.swift), so you don't have to worry about anything except of your cell configuration:
```swift
class ImageTableViewCell: UITableViewCell, ConfigurableCell {
func configure(with url: NSURL) {
loadImageAsync(url: url, imageView: imageView)
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.layoutIfNeeded()
multilineLabel.preferredMaxLayoutWidth = multilineLabel.bounds.size.width
}
}
```
You have to additionally set `preferredMaxLayoutWidth` for all your multiline labels.
## Functional programming
It's never been so easy to deal with table views.
```swift
let users = /* some users array */
let click = TableRowAction<UserTableViewCell>(.click) {
}
let rows = users.filter({ $0.state == .active }).map({ TableRow<UserTableViewCell>(item: $0.name, actions: [click]) })
tableDirector += rows
```
Done, your table is ready.
## 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:
```ruby
MyTableViewCell.swift
MyTableViewCell.xib
```
You can also turn off this behaviour:
```swift
let tableDirector = TableDirector(tableView: tableView, shouldUseAutomaticCellRegistration: false)
```
and register your cell manually.
# Installation
## CocoaPods
To integrate TableKit into your Xcode project using CocoaPods, specify it in your `Podfile`:
```ruby
pod 'TableKit'
```
## Carthage
Add the line `github "maxsokolov/tablekit"` to your `Cartfile`.
## Manual
Clone the repo and drag files from `Sources` folder into your Xcode project.
# Requirements
- iOS 8.0
- Xcode 9.0
# Changelog
Keep an eye on [changes](CHANGELOG.md).
# License
TableKit is available under the MIT license. See LICENSE for details.

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

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

View File

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

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

@ -1,87 +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
open class TablePrototypeCellHeightCalculator: RowHeightCalculator {
private(set) weak var tableView: UITableView?
private var prototypes = [String: UITableViewCell]()
private var cachedHeights = [Int: CGFloat]()
private var separatorHeight = 1 / UIScreen.main.scale
public init(tableView: UITableView?) {
self.tableView = tableView
}
open func height(forRow row: Row, at indexPath: IndexPath) -> CGFloat {
guard let tableView = tableView else { return 0 }
let hash = row.hashValue ^ Int(tableView.bounds.size.width).hashValue
if let height = cachedHeights[hash] {
return height
}
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 }
cell.prepareForReuse()
row.configure(cell)
cell.bounds = CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: cell.bounds.height)
cell.setNeedsLayout()
cell.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + (tableView.separatorStyle != .none ? separatorHeight : 0)
cachedHeights[hash] = height
return height
}
open func estimatedHeight(forRow row: Row, at indexPath: IndexPath) -> CGFloat {
guard let tableView = tableView else { return 0 }
let hash = row.hashValue ^ Int(tableView.bounds.size.width).hashValue
if let height = cachedHeights[hash] {
return height
}
if let estimatedHeight = row.estimatedHeight , estimatedHeight > 0 {
return estimatedHeight
}
return UITableView.automaticDimension
}
open func invalidate() {
cachedHeights.removeAll()
}
}

View File

@ -1,156 +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
open class TableRow<CellType: ConfigurableCell>: Row where CellType: UITableViewCell {
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 {
return ObjectIdentifier(self).hashValue
}
open var reuseIdentifier: String {
return CellType.reuseIdentifier
}
open var estimatedHeight: CGFloat? {
return CellType.estimatedHeight
}
open var defaultHeight: CGFloat? {
return CellType.defaultHeight
}
open var layoutType: LayoutType {
return CellType.layoutType
}
open var cellType: AnyClass {
return CellType.self
}
public init(item: CellType.CellData,
actions: [TableRowAction<CellType>]? = nil) {
self.item = item
actions?.forEach { on($0) }
}
// MARK: - RowConfigurable -
open func configure(_ cell: UITableViewCell) {
(cell as? CellType)?.configure(with: item)
}
// MARK: - RowActionable -
open func invoke(action: TableRowActionType, cell: UITableViewCell?, path: IndexPath, userInfo: [AnyHashable: Any]? = nil) -> Any? {
return actions[action.key]?.compactMap({ $0.invokeActionOn(cell: cell, item: item, path: path, userInfo: userInfo) }).last
}
open func has(action: TableRowActionType) -> Bool {
return actions[action.key] != nil
}
open func isEditingAllowed(forIndexPath indexPath: IndexPath) -> Bool {
if actions[TableRowActionType.canEdit.key] != nil {
return invoke(action: .canEdit, cell: nil, path: indexPath) as? Bool ?? false
}
return !leadingContextualActions.isEmpty
|| !trailingContextualActions.isEmpty
|| actions[TableRowActionType.clickDelete.key] != nil
}
// MARK: - actions -
@discardableResult
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)
return self
}
@discardableResult
open func on<T>(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> T) -> Self {
return on(TableRowAction<CellType>(type, handler: handler))
}
@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

@ -1,83 +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
open class TableRowActionOptions<CellType: ConfigurableCell> where CellType: UITableViewCell {
public let item: CellType.CellData
public let cell: CellType?
public let indexPath: IndexPath
public let userInfo: [AnyHashable: Any]?
init(item: CellType.CellData, cell: CellType?, path: IndexPath, userInfo: [AnyHashable: Any]?) {
self.item = item
self.cell = cell
self.indexPath = path
self.userInfo = userInfo
}
}
private enum TableRowActionHandler<CellType: ConfigurableCell> where CellType: UITableViewCell {
case voidAction((TableRowActionOptions<CellType>) -> Void)
case action((TableRowActionOptions<CellType>) -> Any?)
func invoke(withOptions options: TableRowActionOptions<CellType>) -> Any? {
switch self {
case .voidAction(let handler):
return handler(options)
case .action(let handler):
return handler(options)
}
}
}
open class TableRowAction<CellType: ConfigurableCell> where CellType: UITableViewCell {
open var id: String?
public let type: TableRowActionType
private let handler: TableRowActionHandler<CellType>
public init(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> Void) {
self.type = type
self.handler = .voidAction(handler)
}
public init(_ key: String, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> Void) {
self.type = .custom(key)
self.handler = .voidAction(handler)
}
public init<T>(_ type: TableRowActionType, handler: @escaping (_ options: TableRowActionOptions<CellType>) -> T) {
self.type = type
self.handler = .action(handler)
}
public func invokeActionOn(cell: UITableViewCell?, item: CellType.CellData, path: IndexPath, userInfo: [AnyHashable: Any]?) -> Any? {
return handler.invoke(withOptions: TableRowActionOptions(item: item, cell: cell as? CellType, path: path, userInfo: userInfo))
}
}

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 = '0.6.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 = 'TableKit/*.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

@ -1,502 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
2CBFA2F521F692F100147B56 /* ExpandableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBFA2F421F692F100147B56 /* ExpandableState.swift */; };
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */; };
3201E78621BE9E25001DF9E7 /* UITableViewCell+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */; };
3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78721BE9EB2001DF9E7 /* Expandable.swift */; };
3201E78A21BE9ED4001DF9E7 /* ExpandableCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */; };
32BDFE9F21C167F400D0BBB4 /* LayoutType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */; };
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */; };
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E858571DB153F500A9AA55 /* TableKit.swift */; };
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */; };
DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */; };
DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A81D0EC2C90021F650 /* Operators.swift */; };
DA9EA7B21D0EC2C90021F650 /* TableCellAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */; };
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */; };
DA9EA7B41D0EC2C90021F650 /* TableRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */; };
DA9EA7B51D0EC2C90021F650 /* TableRowAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */; };
DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */; };
DA9EA7C91D0EC45F0021F650 /* TableKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9EA7561D0B679A0021F650 /* TableKit.framework */; };
DA9EA7CF1D0EC4930021F650 /* TableKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7BE1D0EC41D0021F650 /* TableKitTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
DA9EA7CA1D0EC45F0021F650 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DA9EA74D1D0B679A0021F650 /* Project object */;
proxyType = 1;
remoteGlobalIDString = DA9EA7551D0B679A0021F650;
remoteInfo = TableKit;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
2CBFA2F421F692F100147B56 /* ExpandableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableState.swift; sourceTree = "<group>"; };
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellHeightCalculator.swift; sourceTree = "<group>"; };
3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+Extensions.swift"; sourceTree = "<group>"; };
3201E78721BE9EB2001DF9E7 /* Expandable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expandable.swift; sourceTree = "<group>"; };
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellViewModel.swift; sourceTree = "<group>"; };
32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutType.swift; sourceTree = "<group>"; };
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellRegisterer.swift; sourceTree = "<group>"; };
50E858571DB153F500A9AA55 /* TableKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableKit.swift; sourceTree = "<group>"; };
DA9EA7561D0B679A0021F650 /* TableKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TableKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = "<group>"; };
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TablePrototypeCellHeightCalculator.swift; sourceTree = "<group>"; };
DA9EA7A81D0EC2C90021F650 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellAction.swift; sourceTree = "<group>"; };
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableDirector.swift; sourceTree = "<group>"; };
DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRow.swift; sourceTree = "<group>"; };
DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowAction.swift; sourceTree = "<group>"; };
DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableSection.swift; sourceTree = "<group>"; };
DA9EA7B91D0EC34E0021F650 /* TableKit.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TableKit.plist; sourceTree = "<group>"; };
DA9EA7BA1D0EC34E0021F650 /* TableKitTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TableKitTests.plist; sourceTree = "<group>"; };
DA9EA7BE1D0EC41D0021F650 /* TableKitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableKitTests.swift; sourceTree = "<group>"; };
DA9EA7C41D0EC45F0021F650 /* TableKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TableKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
DA9EA7521D0B679A0021F650 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
DA9EA7C11D0EC45F0021F650 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DA9EA7C91D0EC45F0021F650 /* TableKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
DA9EA74C1D0B679A0021F650 = {
isa = PBXGroup;
children = (
DA9EA7B81D0EC31B0021F650 /* Configs */,
DA9EA7571D0B679A0021F650 /* Products */,
DA9EA7A51D0EC2B90021F650 /* Sources */,
DA9EA7BD1D0EC3D70021F650 /* Tests */,
);
sourceTree = "<group>";
};
DA9EA7571D0B679A0021F650 /* Products */ = {
isa = PBXGroup;
children = (
DA9EA7561D0B679A0021F650 /* TableKit.framework */,
DA9EA7C41D0EC45F0021F650 /* TableKitTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
DA9EA7A51D0EC2B90021F650 /* Sources */ = {
isa = PBXGroup;
children = (
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */,
3201E78321BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift */,
DA9EA7A81D0EC2C90021F650 /* Operators.swift */,
DA9EA7A91D0EC2C90021F650 /* TableCellAction.swift */,
50CF6E6A1D6704FE004746FF /* TableCellRegisterer.swift */,
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */,
50E858571DB153F500A9AA55 /* TableKit.swift */,
DA9EA7A71D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift */,
DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */,
DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */,
DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */,
3201E78521BE9E25001DF9E7 /* UITableViewCell+Extensions.swift */,
3201E78721BE9EB2001DF9E7 /* Expandable.swift */,
3201E78921BE9ED4001DF9E7 /* ExpandableCellViewModel.swift */,
32BDFE9E21C167F400D0BBB4 /* LayoutType.swift */,
2CBFA2F421F692F100147B56 /* ExpandableState.swift */,
);
path = Sources;
sourceTree = "<group>";
};
DA9EA7B81D0EC31B0021F650 /* Configs */ = {
isa = PBXGroup;
children = (
DA9EA7B91D0EC34E0021F650 /* TableKit.plist */,
DA9EA7BA1D0EC34E0021F650 /* TableKitTests.plist */,
);
path = Configs;
sourceTree = "<group>";
};
DA9EA7BD1D0EC3D70021F650 /* Tests */ = {
isa = PBXGroup;
children = (
DA9EA7BE1D0EC41D0021F650 /* TableKitTests.swift */,
);
path = Tests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
DA9EA7531D0B679A0021F650 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
DA9EA7551D0B679A0021F650 /* TableKit */ = {
isa = PBXNativeTarget;
buildConfigurationList = DA9EA75E1D0B679A0021F650 /* Build configuration list for PBXNativeTarget "TableKit" */;
buildPhases = (
DA9EA7511D0B679A0021F650 /* Sources */,
DA9EA7521D0B679A0021F650 /* Frameworks */,
DA9EA7531D0B679A0021F650 /* Headers */,
DA9EA7541D0B679A0021F650 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TableKit;
productName = TableKit;
productReference = DA9EA7561D0B679A0021F650 /* TableKit.framework */;
productType = "com.apple.product-type.framework";
};
DA9EA7C31D0EC45F0021F650 /* TableKitTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = DA9EA7CC1D0EC45F0021F650 /* Build configuration list for PBXNativeTarget "TableKitTests" */;
buildPhases = (
DA9EA7C01D0EC45F0021F650 /* Sources */,
DA9EA7C11D0EC45F0021F650 /* Frameworks */,
DA9EA7C21D0EC45F0021F650 /* Resources */,
);
buildRules = (
);
dependencies = (
DA9EA7CB1D0EC45F0021F650 /* PBXTargetDependency */,
);
name = TableKitTests;
productName = TableKitTests;
productReference = DA9EA7C41D0EC45F0021F650 /* TableKitTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
DA9EA74D1D0B679A0021F650 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 1000;
ORGANIZATIONNAME = "Max Sokolov";
TargetAttributes = {
DA9EA7551D0B679A0021F650 = {
CreatedOnToolsVersion = 7.3;
LastSwiftMigration = 1000;
};
DA9EA7C31D0EC45F0021F650 = {
CreatedOnToolsVersion = 7.3;
LastSwiftMigration = 1000;
};
};
};
buildConfigurationList = DA9EA7501D0B679A0021F650 /* Build configuration list for PBXProject "TableKit" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = DA9EA74C1D0B679A0021F650;
productRefGroup = DA9EA7571D0B679A0021F650 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
DA9EA7551D0B679A0021F650 /* TableKit */,
DA9EA7C31D0EC45F0021F650 /* TableKitTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
DA9EA7541D0B679A0021F650 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
DA9EA7C21D0EC45F0021F650 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
DA9EA7511D0B679A0021F650 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3201E78A21BE9ED4001DF9E7 /* ExpandableCellViewModel.swift in Sources */,
50CF6E6B1D6704FE004746FF /* TableCellRegisterer.swift in Sources */,
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */,
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */,
3201E78821BE9EB2001DF9E7 /* Expandable.swift in Sources */,
2CBFA2F521F692F100147B56 /* ExpandableState.swift in Sources */,
DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */,
DA9EA7B01D0EC2C90021F650 /* TablePrototypeCellHeightCalculator.swift in Sources */,
3201E78421BE9DE1001DF9E7 /* ExpandableCellHeightCalculator.swift in Sources */,
DA9EA7B51D0EC2C90021F650 /* TableRowAction.swift in Sources */,
DA9EA7B21D0EC2C90021F650 /* TableCellAction.swift in Sources */,
32BDFE9F21C167F400D0BBB4 /* LayoutType.swift in Sources */,
3201E78621BE9E25001DF9E7 /* UITableViewCell+Extensions.swift in Sources */,
DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */,
DA9EA7B41D0EC2C90021F650 /* TableRow.swift in Sources */,
50E858581DB153F500A9AA55 /* TableKit.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA9EA7C01D0EC45F0021F650 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DA9EA7CF1D0EC4930021F650 /* TableKitTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
DA9EA7CB1D0EC45F0021F650 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DA9EA7551D0B679A0021F650 /* TableKit */;
targetProxy = DA9EA7CA1D0EC45F0021F650 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
DA9EA75C1D0B679A0021F650 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
DA9EA75D1D0B679A0021F650 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
DA9EA75F1D0B679A0021F650 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/Configs/TableKit.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
DA9EA7601D0B679A0021F650 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/Configs/TableKit.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
DA9EA7CD1D0EC45F0021F650 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
INFOPLIST_FILE = Configs/TableKitTests.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
DA9EA7CE1D0EC45F0021F650 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
INFOPLIST_FILE = Configs/TableKitTests.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
DA9EA7501D0B679A0021F650 /* Build configuration list for PBXProject "TableKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA9EA75C1D0B679A0021F650 /* Debug */,
DA9EA75D1D0B679A0021F650 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
DA9EA75E1D0B679A0021F650 /* Build configuration list for PBXNativeTarget "TableKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA9EA75F1D0B679A0021F650 /* Debug */,
DA9EA7601D0B679A0021F650 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
DA9EA7CC1D0EC45F0021F650 /* Build configuration list for PBXNativeTarget "TableKitTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA9EA7CD1D0EC45F0021F650 /* Debug */,
DA9EA7CE1D0EC45F0021F650 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = DA9EA74D1D0B679A0021F650 /* Project object */;
}

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

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,99 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA9EA7551D0B679A0021F650"
BuildableName = "TableKit.framework"
BlueprintName = "TableKit"
ReferencedContainer = "container:TableKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA9EA7C31D0EC45F0021F650"
BuildableName = "TableKitTests.xctest"
BlueprintName = "TableKitTests"
ReferencedContainer = "container:TableKit.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA9EA7551D0B679A0021F650"
BuildableName = "TableKit.framework"
BlueprintName = "TableKit"
ReferencedContainer = "container:TableKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA9EA7551D0B679A0021F650"
BuildableName = "TableKit.framework"
BlueprintName = "TableKit"
ReferencedContainer = "container:TableKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA9EA7551D0B679A0021F650"
BuildableName = "TableKit.framework"
BlueprintName = "TableKit"
ReferencedContainer = "container:TableKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:TableKit/TableKit.xcodeproj">
</FileRef>
<FileRef
location = "group:TableKitDemo/TableKitDemo.xcodeproj">
</FileRef>
</Workspace>

View File

@ -21,37 +21,26 @@
import UIKit import UIKit
public protocol ConfigurableCell { public protocol ConfigurableCell {
associatedtype CellData associatedtype T
static var reuseIdentifier: String { get } static func reusableIdentifier() -> String
static func estimatedHeight() -> CGFloat
static var estimatedHeight: CGFloat? { get } static func defaultHeight() -> CGFloat?
func configure(_: T)
static var defaultHeight: CGFloat? { get }
static var layoutType: LayoutType { get }
func configure(with _: CellData)
} }
public extension ConfigurableCell where Self: UITableViewCell { public extension ConfigurableCell where Self: UITableViewCell {
static var reuseIdentifier: String { static func reusableIdentifier() -> String {
return String(describing: self) return String(self)
} }
static var estimatedHeight: CGFloat? { static func estimatedHeight() -> CGFloat {
return UITableViewAutomaticDimension
}
static func defaultHeight() -> CGFloat? {
return nil return nil
} }
}
static var defaultHeight: CGFloat? {
return nil
}
static var layoutType: LayoutType {
return .auto
}
}

View File

@ -20,39 +20,29 @@
import UIKit import UIKit
class TableCellRegisterer { public protocol HeightStrategy {
private var registeredIds = Set<String>() var tableView: UITableView? { get set }
private weak var tableView: UITableView?
func height<Item, Cell: ConfigurableCell where Cell.T == Item, Cell: UITableViewCell>(item: Item, indexPath: NSIndexPath, cell: Cell.Type) -> CGFloat
init(tableView: UITableView?) {
self.tableView = tableView
}
func register(cellType: AnyClass, forCellReuseIdentifier reuseIdentifier: String) {
if registeredIds.contains(reuseIdentifier) {
return
}
// check if cell is already registered, probably cell has been registered by storyboard
if tableView?.dequeueReusableCell(withIdentifier: reuseIdentifier) != nil {
registeredIds.insert(reuseIdentifier)
return
}
let bundle = Bundle(for: cellType)
// we hope that cell's xib file has name that equals to cell's class name
// in that case we could register nib
if let _ = bundle.path(forResource: reuseIdentifier, ofType: "nib") {
tableView?.register(UINib(nibName: reuseIdentifier, bundle: bundle), forCellReuseIdentifier: reuseIdentifier)
// otherwise, register cell class
} else {
tableView?.register(cellType, forCellReuseIdentifier: reuseIdentifier)
}
registeredIds.insert(reuseIdentifier)
}
} }
public class PrototypeHeightStrategy: HeightStrategy {
public weak var tableView: UITableView?
private var cachedHeights = [Int: CGFloat]()
public func height<Item, Cell: ConfigurableCell where Cell.T == Item, Cell: UITableViewCell>(item: Item, indexPath: NSIndexPath, cell: Cell.Type) -> CGFloat {
guard let cell = tableView?.dequeueReusableCellWithIdentifier(Cell.reusableIdentifier()) as? Cell else { return 0 }
cell.bounds = CGRectMake(0, 0, tableView?.bounds.size.width ?? 0, cell.bounds.height)
cell.configure(item)
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
}
}

View File

@ -18,29 +18,37 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // 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. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// -- // -
public func +=(left: TableDirector, right: TableSection) { /*public func +=(left: TableDirector, right: RowBuilder) {
left.append(section: TableSectionBuilder(rows: [right]))
}
public func +=(left: TableDirector, right: [RowBuilder]) {
left.append(section: TableSectionBuilder(rows: right))
}
public func +=(left: TableDirector, right: TableSectionBuilder) {
left.append(section: right) left.append(section: right)
} }
public func +=(left: TableDirector, right: [TableSection]) { public func +=(left: TableDirector, right: [TableSectionBuilder]) {
left.append(sections: right) left.append(sections: right)
} }
// -- // -
public func +=(left: TableDirector, right: Row) { public func +=<DataType, CellType>(left: TableRowBuilder<DataType, CellType>, right: DataType) {
left.append(sections: [TableSection(rows: [right])]) left.append(items: [right])
} }
public func +=(left: TableDirector, right: [Row]) { public func +=<DataType, CellType>(left: TableRowBuilder<DataType, CellType>, right: [DataType]) {
left.append(sections: [TableSection(rows: right)]) left.append(items: right)
} }
// -- // -
public func +=(left: TableSection, right: Row) { public func +=(left: TableSectionBuilder, right: RowBuilder) {
left.append(row: right) left.append(row: right)
} }
public func +=(left: TableSection, right: [Row]) { public func +=(left: TableSectionBuilder, right: [RowBuilder]) {
left.append(rows: right) left.append(rows: right)
} }*/

View File

@ -0,0 +1,184 @@
//
// 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
/**
Responsible for table view's datasource and delegate.
*/
public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
public private(set) weak var tableView: UITableView?
private weak var scrollDelegate: UIScrollViewDelegate?
public private(set) var sections = [TableSection]()
public init(tableView: UITableView, scrollDelegate: UIScrollViewDelegate? = nil) {
super.init()
self.scrollDelegate = scrollDelegate
self.tableView = tableView
self.tableView?.delegate = self
self.tableView?.dataSource = self
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(didReceiveAction), name: TabletNotifications.CellAction, object: nil)
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
public func reload() {
tableView?.reloadData()
}
// MARK: Public
public func invoke(action action: TableRowActionType, cell: UITableViewCell?, indexPath: NSIndexPath) -> Any? {
return sections[indexPath.section].items[indexPath.row].invoke(action, cell: cell, path: indexPath)
}
public override func respondsToSelector(selector: Selector) -> Bool {
return super.respondsToSelector(selector) || scrollDelegate?.respondsToSelector(selector) == true
}
public override func forwardingTargetForSelector(selector: Selector) -> AnyObject? {
return scrollDelegate?.respondsToSelector(selector) == true ? scrollDelegate : super.forwardingTargetForSelector(selector)
}
// MARK: - Internal -
func didReceiveAction(notification: NSNotification) {
if let action = notification.object as? Action, indexPath = tableView?.indexPathForCell(action.cell) {
//let builder = builderAtIndexPath(indexPath)
//builder.0.invoke(action: .custom(action.key), cell: action.cell, indexPath: indexPath, itemIndex: builder.1, userInfo: notification.userInfo)
}
}
// MARK: - Height
public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return sections[indexPath.section].items[indexPath.row].estimatedHeight
}
public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return sections[indexPath.section].items[indexPath.row].defaultHeight
}
// MARK: UITableViewDataSource - configuration
public func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count
}
public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].numberOfRows
}
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = sections[indexPath.section].items[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier(row.reusableIdentifier, forIndexPath: indexPath)
if cell.frame.size.width != tableView.frame.size.width {
cell.frame = CGRectMake(0, 0, tableView.frame.size.width, cell.frame.size.height)
cell.layoutIfNeeded()
}
row.configure(cell)
return cell
}
// MARK: UITableViewDataSource - section setup
public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].headerTitle
}
public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return sections[section].footerTitle
}
// MARK: UITableViewDelegate - section setup
public func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return sections[section].headerView
}
public func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return sections[section].footerView
}
public func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return sections[section].headerView?.frame.size.height ?? UITableViewAutomaticDimension
}
public func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return sections[section].footerView?.frame.size.height ?? UITableViewAutomaticDimension
}
// MARK: UITableViewDelegate - actions
public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
if invoke(action: .click, cell: cell, indexPath: indexPath) != nil {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
} else {
invoke(action: .select, cell: cell, indexPath: indexPath)
}
}
public func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
invoke(action: .deselect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath)
}
public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
invoke(action: .willDisplay, cell: cell, indexPath: indexPath)
}
public func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return invoke(action: .shouldHighlight, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? true
}
/*func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
return invokeAction(.willSelect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? NSIndexPath
}*/
// MARK: - Sections manipulation -
public func append(section section: TableSection) {
append(sections: [section])
}
public func append(sections sections: [TableSection]) {
sections.forEach { $0.tableDirector = self }
self.sections.appendContentsOf(sections)
}
public func clear() {
sections.removeAll()
}
}

19
TableKit/TableKit.h Normal file
View File

@ -0,0 +1,19 @@
//
// TableKit.h
// TableKit
//
// Created by Max Sokolov on 11/06/16.
// Copyright © 2016 Max Sokolov. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for TableKit.
FOUNDATION_EXPORT double TableKitVersionNumber;
//! Project version string for TableKit.
FOUNDATION_EXPORT const unsigned char TableKitVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <TableKit/PublicHeader.h>

View File

@ -0,0 +1,335 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
DA9EA7711D0B68460021F650 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7681D0B68460021F650 /* ConfigurableCell.swift */; };
DA9EA7721D0B68460021F650 /* HeightStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7691D0B68460021F650 /* HeightStrategy.swift */; };
DA9EA7731D0B68460021F650 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA76A1D0B68460021F650 /* Operators.swift */; };
DA9EA7741D0B68460021F650 /* TableDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA76B1D0B68460021F650 /* TableDirector.swift */; };
DA9EA7751D0B68460021F650 /* TableRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA76C1D0B68460021F650 /* TableRow.swift */; };
DA9EA7761D0B68460021F650 /* TableRowAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA76D1D0B68460021F650 /* TableRowAction.swift */; };
DA9EA7771D0B68460021F650 /* TableRowBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA76E1D0B68460021F650 /* TableRowBuilder.swift */; };
DA9EA7781D0B68460021F650 /* TableSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA76F1D0B68460021F650 /* TableSection.swift */; };
DA9EA7791D0B68460021F650 /* Tablet.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7701D0B68460021F650 /* Tablet.swift */; };
DA9EA7801D0B689C0021F650 /* TableKit.h in Headers */ = {isa = PBXBuildFile; fileRef = DA9EA77E1D0B689C0021F650 /* TableKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
DA9EA7561D0B679A0021F650 /* TableKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TableKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DA9EA7681D0B68460021F650 /* ConfigurableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = "<group>"; };
DA9EA7691D0B68460021F650 /* HeightStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeightStrategy.swift; sourceTree = "<group>"; };
DA9EA76A1D0B68460021F650 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
DA9EA76B1D0B68460021F650 /* TableDirector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableDirector.swift; sourceTree = "<group>"; };
DA9EA76C1D0B68460021F650 /* TableRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRow.swift; sourceTree = "<group>"; };
DA9EA76D1D0B68460021F650 /* TableRowAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowAction.swift; sourceTree = "<group>"; };
DA9EA76E1D0B68460021F650 /* TableRowBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowBuilder.swift; sourceTree = "<group>"; };
DA9EA76F1D0B68460021F650 /* TableSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableSection.swift; sourceTree = "<group>"; };
DA9EA7701D0B68460021F650 /* Tablet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tablet.swift; sourceTree = "<group>"; };
DA9EA77D1D0B689C0021F650 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DA9EA77E1D0B689C0021F650 /* TableKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableKit.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
DA9EA7521D0B679A0021F650 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
DA9EA74C1D0B679A0021F650 = {
isa = PBXGroup;
children = (
DA9EA7671D0B68340021F650 /* Classes */,
DA9EA7571D0B679A0021F650 /* Products */,
DA9EA77C1D0B68860021F650 /* Supporting Files */,
);
sourceTree = "<group>";
};
DA9EA7571D0B679A0021F650 /* Products */ = {
isa = PBXGroup;
children = (
DA9EA7561D0B679A0021F650 /* TableKit.framework */,
);
name = Products;
sourceTree = "<group>";
};
DA9EA7671D0B68340021F650 /* Classes */ = {
isa = PBXGroup;
children = (
DA9EA7681D0B68460021F650 /* ConfigurableCell.swift */,
DA9EA7691D0B68460021F650 /* HeightStrategy.swift */,
DA9EA76A1D0B68460021F650 /* Operators.swift */,
DA9EA76B1D0B68460021F650 /* TableDirector.swift */,
DA9EA76C1D0B68460021F650 /* TableRow.swift */,
DA9EA76D1D0B68460021F650 /* TableRowAction.swift */,
DA9EA76E1D0B68460021F650 /* TableRowBuilder.swift */,
DA9EA76F1D0B68460021F650 /* TableSection.swift */,
DA9EA7701D0B68460021F650 /* Tablet.swift */,
);
name = Classes;
sourceTree = "<group>";
};
DA9EA77C1D0B68860021F650 /* Supporting Files */ = {
isa = PBXGroup;
children = (
DA9EA77D1D0B689C0021F650 /* Info.plist */,
DA9EA77E1D0B689C0021F650 /* TableKit.h */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
DA9EA7531D0B679A0021F650 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
DA9EA7801D0B689C0021F650 /* TableKit.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
DA9EA7551D0B679A0021F650 /* TableKit */ = {
isa = PBXNativeTarget;
buildConfigurationList = DA9EA75E1D0B679A0021F650 /* Build configuration list for PBXNativeTarget "TableKit" */;
buildPhases = (
DA9EA7511D0B679A0021F650 /* Sources */,
DA9EA7521D0B679A0021F650 /* Frameworks */,
DA9EA7531D0B679A0021F650 /* Headers */,
DA9EA7541D0B679A0021F650 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TableKit;
productName = TableKit;
productReference = DA9EA7561D0B679A0021F650 /* TableKit.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
DA9EA74D1D0B679A0021F650 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0730;
ORGANIZATIONNAME = "Max Sokolov";
TargetAttributes = {
DA9EA7551D0B679A0021F650 = {
CreatedOnToolsVersion = 7.3;
};
};
};
buildConfigurationList = DA9EA7501D0B679A0021F650 /* Build configuration list for PBXProject "TableKit" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = DA9EA74C1D0B679A0021F650;
productRefGroup = DA9EA7571D0B679A0021F650 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
DA9EA7551D0B679A0021F650 /* TableKit */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
DA9EA7541D0B679A0021F650 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
DA9EA7511D0B679A0021F650 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DA9EA7711D0B68460021F650 /* ConfigurableCell.swift in Sources */,
DA9EA7721D0B68460021F650 /* HeightStrategy.swift in Sources */,
DA9EA7781D0B68460021F650 /* TableSection.swift in Sources */,
DA9EA7751D0B68460021F650 /* TableRow.swift in Sources */,
DA9EA7761D0B68460021F650 /* TableRowAction.swift in Sources */,
DA9EA7741D0B68460021F650 /* TableDirector.swift in Sources */,
DA9EA7771D0B68460021F650 /* TableRowBuilder.swift in Sources */,
DA9EA7791D0B68460021F650 /* Tablet.swift in Sources */,
DA9EA7731D0B68460021F650 /* Operators.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
DA9EA75C1D0B679A0021F650 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
DA9EA75D1D0B679A0021F650 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
DA9EA75F1D0B679A0021F650 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
DA9EA7601D0B679A0021F650 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tablekit.TableKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
DA9EA7501D0B679A0021F650 /* Build configuration list for PBXProject "TableKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA9EA75C1D0B679A0021F650 /* Debug */,
DA9EA75D1D0B679A0021F650 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
DA9EA75E1D0B679A0021F650 /* Build configuration list for PBXNativeTarget "TableKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA9EA75F1D0B679A0021F650 /* Debug */,
DA9EA7601D0B679A0021F650 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = DA9EA74D1D0B679A0021F650 /* Project object */;
}

85
TableKit/TableRow.swift Normal file
View File

@ -0,0 +1,85 @@
//
// 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
public protocol RowConfigurable {
func configure(cell: UITableViewCell)
}
public protocol RowActionable {
func invoke(action: TableRowActionType, cell: UITableViewCell?, path: NSIndexPath) -> Any?
func hasAction(action: TableRowActionType) -> Bool
}
public protocol Row: RowConfigurable, RowActionable {
var reusableIdentifier: String { get }
var estimatedHeight: CGFloat { get }
var defaultHeight: CGFloat { get }
}
public class TableRow<ItemType, CellType: ConfigurableCell where CellType.T == ItemType, CellType: UITableViewCell>: Row {
public let item: ItemType
private var actions = [String: RowAction]()
public var reusableIdentifier: String {
return CellType.reusableIdentifier()
}
public var estimatedHeight: CGFloat {
return CellType.estimatedHeight()
}
public var defaultHeight: CGFloat {
return CellType.defaultHeight() ?? UITableViewAutomaticDimension
}
public init(item: ItemType, actions: [TableRowAction<ItemType, CellType>]? = nil) {
self.item = item
}
// MARK: - RowConfigurable -
public func configure(cell: UITableViewCell) {
(cell as? CellType)?.configure(item)
}
// MARK: - RowActionable -
public func invoke(action: TableRowActionType, cell: UITableViewCell?, path: NSIndexPath) -> Any? {
return actions[action.key]?.invoke()
}
public func hasAction(action: TableRowActionType) -> Bool {
return actions[action.key] != nil
}
// MARK: - actions -
public func action(action: TableRowAction<ItemType, CellType>) -> Self {
actions[action.type.key] = action
return self
}
}

View File

@ -0,0 +1,68 @@
//
// 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
public enum TableRowActionType {
case click
case select
case deselect
case willSelect
case configure
case willDisplay
case shouldHighlight
case height
case custom(String)
var key: String {
switch (self) {
case .custom(let key):
return key
default:
return "_\(self)"
}
}
}
protocol RowAction {
func invoke() -> Any?
}
public class TableRowAction<ItemType, CellType: ConfigurableCell where CellType.T == ItemType, CellType: UITableViewCell>: RowAction {
public let type: TableRowActionType
public init(_ type: TableRowActionType, handler: (row: TableRow<ItemType, CellType>) -> Void) {
self.type = type
}
public init<T>(_ type: TableRowActionType, handler: (row: TableRow<ItemType, CellType>) -> T) {
self.type = type
}
// MARK: - RowAction -
func invoke() -> Any? {
return nil
}
}

View File

@ -0,0 +1,168 @@
//
// 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
public typealias ReturnValue = AnyObject?
/**
Responsible for building cells of given type and passing items to them.
*/
/*public class TableRowBuilder<DataType, CellType: ConfigurableCell where CellType.T == DataType, CellType: UITableViewCell> : RowBuilder {
public private(set) weak var tableDirector: TableDirector?
private var heightStrategy: HeightStrategy?
private var actions = [String: ActionHandler<DataType, CellType>]()
private var items = [DataType]()
public func reusableIdentifier(_: Int) -> String {
return CellType.reusableIdentifier()
}
public var numberOfRows: Int {
return items.count
}
// MARK: - Initializers -
public init(item: DataType) {
items.append(item)
}
public init(items: [DataType]? = nil) {
if let items = items {
self.items.appendContentsOf(items)
}
}
// MARK: - RowHeightCalculatable -
public func rowHeight(index: Int, indexPath: NSIndexPath) -> CGFloat {
return CellType.defaultHeight() ?? heightStrategy?.height(item(index: index), indexPath: indexPath, cell: CellType.self) ?? UITableViewAutomaticDimension
}
public func estimatedRowHeight(index: Int, indexPath: NSIndexPath) -> CGFloat {
return CellType.estimatedHeight()
}
// MARK: - RowConfigurable -
public func configure(cell: UITableViewCell, path: NSIndexPath, index: Int) {
(cell as? CellType)?.configure(items[index])
}
// MARK: - Chaining actions -
public func addAction(action: TableRowAction<DataType, CellType>) {
}
public func action(key: String, handler: (data: ActionData<DataType, CellType>) -> Void) -> Self {
actions[key] = .Handler(handler)
return self
}
public func action(type: ActionType, handler: (data: ActionData<DataType, CellType>) -> Void) -> Self {
actions[type.key] = .Handler(handler)
return self
}
public func valueAction(type: ActionType, handler: (data: ActionData<DataType, CellType>) -> ReturnValue) -> Self {
actions[type.key] = .ValueHandler(handler)
return self
}
public func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? {
if case .configure = action {
(cell as? CellType)?.configure(item(index: itemIndex))
}
if let action = actions[action.key] {
return action.invoke(ActionData(cell: cell as? CellType, indexPath: indexPath, item: items[itemIndex], itemIndex: itemIndex, userInfo: userInfo))
}
return nil
}
private func registerCell(inTableView tableView: UITableView) {
if tableView.dequeueReusableCellWithIdentifier(reusableIdentifier(0)) != nil {
return
}
let resource = String(CellType)
let bundle = NSBundle(forClass: CellType.self)
if let _ = bundle.pathForResource(resource, ofType: "nib") { // existing cell
tableView.registerNib(UINib(nibName: resource, bundle: bundle), forCellReuseIdentifier: reusableIdentifier(0))
} else {
tableView.registerClass(CellType.self, forCellReuseIdentifier: reusableIdentifier(0))
}
}
public func willUpdateDirector(director: TableDirector?) {
tableDirector = director
heightStrategy = PrototypeHeightStrategy()
heightStrategy?.tableView = director?.tableView
}
// MARK: - Items manipulation -
public func delete(indexes indexes: [Int], animated: UITableViewRowAnimation) -> Self {
return self
}
public func insert(items: [DataType], atIndex index: Int, animated: UITableViewRowAnimation) -> Self {
self.items.insertContentsOf(items, at: index)
return self
}
public func move(indexes: [Int], toIndexes: [Int]) -> Self {
return self
}
public func update(index index: Int, item: DataType, animated: UITableViewRowAnimation) -> Self {
return self
}
public func item(index index: Int) -> DataType {
return items[index]
}
public func append(items items: [DataType]) {
self.items.appendContentsOf(items)
}
public func clear() {
items.removeAll()
}
}*/

View File

@ -20,43 +20,42 @@
import UIKit import UIKit
open class TableSection { /**
Responsible for building a certain table view section.
Can host several row builders.
*/
public class TableSection {
open private(set) var rows = [Row]() weak var tableDirector: TableDirector?
open var headerTitle: String? public private(set) var items = [Row]()
open var footerTitle: String?
open var indexTitle: String? public var headerTitle: String?
public var footerTitle: String?
open var headerView: UIView?
open var footerView: UIView? public private(set) var headerView: UIView?
public private(set) var footerView: UIView?
open var headerHeight: CGFloat? = nil
open var footerHeight: CGFloat? = nil /// A total number of rows in section of each row builder.
public var numberOfRows: Int {
open var numberOfRows: Int { return items.count
return rows.count
}
open var isEmpty: Bool {
return rows.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)
} }
} }
public convenience init(headerTitle: String?, footerTitle: String?, rows: [Row]? = nil) { public convenience init(headerTitle: String?, footerTitle: String?, rows: [Row]?) {
self.init(rows: rows) self.init(rows: rows)
self.headerTitle = headerTitle self.headerTitle = headerTitle
self.footerTitle = footerTitle self.footerTitle = footerTitle
} }
public convenience init(headerView: UIView?, footerView: UIView?, rows: [Row]? = nil) { public convenience init(headerView: UIView?, footerView: UIView?, rows: [Row]?) {
self.init(rows: rows) self.init(rows: rows)
self.headerView = headerView self.headerView = headerView
@ -64,47 +63,18 @@ open class TableSection {
} }
// 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)
//if let director = tableDirector { rows.forEach { $0.willUpdateDirector(director) } }
//builders.appendContentsOf(rows)
} }
}
open func insert(row: Row, at index: Int) {
rows.insert(row, at: index)
}
open func insert(rows: [Row], at index: Int) {
self.rows.insert(contentsOf: rows, at: index)
}
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

@ -20,11 +20,35 @@
import UIKit import UIKit
struct TabletNotifications {
static let CellAction = "TabletNotificationsCellAction"
}
public class ActionData<DataType, CellType> {
public let cell: CellType?
public let item: DataType
public let itemIndex: Int
public let indexPath: NSIndexPath
public let userInfo: [NSObject: AnyObject]?
init(cell: CellType?, indexPath: NSIndexPath, item: DataType, itemIndex: Int, userInfo: [NSObject: AnyObject]?) {
self.cell = cell
self.indexPath = indexPath
self.item = item
self.itemIndex = itemIndex
self.userInfo = userInfo
}
}
/** /**
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 Action {
/// The cell that triggers an action. /// The cell that triggers an action.
public let cell: UITableViewCell public let cell: UITableViewCell
@ -33,16 +57,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(TabletNotifications.CellAction, object: self, userInfo: userInfo)
} }
} }

View File

@ -0,0 +1,15 @@
//
// AppDelegate.swift
// TabletDemo
//
// Created by Max Sokolov on 08/11/15.
// Copyright © 2015 Tablet. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
}

View File

@ -0,0 +1,33 @@
//
// HeaderFooterController.swift
// TabletDemo
//
// Created by Max Sokolov on 16/04/16.
// Copyright © 2016 Tablet. All rights reserved.
//
import UIKit
import TableKit
class HeaderFooterController: UIViewController {
@IBOutlet weak var tableView: UITableView! {
didSet {
tableDirector = TableDirector(tableView: tableView)
}
}
var tableDirector: TableDirector!
override func viewDidLoad() {
super.viewDidLoad()
//let rows = TableRowBuilder<String, StoryboardTableViewCell>(items: ["3", "4", "5"])
//let headerView = UIView(frame: CGRectMake(0, 0, 100, 100))
//headerView.backgroundColor = UIColor.lightGrayColor()
//let section = TableSectionBuilder(headerView: headerView, footerView: nil, rows: [rows])
//tableDirector += section
}
}

View File

@ -0,0 +1,101 @@
//
// MainController.swift
// TabletDemo
//
// Created by Max Sokolov on 16/04/16.
// Copyright © 2016 Tablet. All rights reserved.
//
import UIKit
import TableKit
class MainController: UIViewController {
@IBOutlet weak var tableView: UITableView! {
didSet {
tableDirector = TableDirector(tableView: tableView)
}
}
var tableDirector: TableDirector!
override func viewDidLoad() {
super.viewDidLoad()
/*let rowBuilder = TableRowBuilder<String, StoryboardImageTableViewCell>(items: ["1", "1", "1", "1"])
.action(.click) { [unowned self] data in
self.performSegueWithIdentifier("headerfooter", sender: nil)
}*/
let a = TableRowAction<String, StoryboardImageTableViewCell>(.click) {
(row) in
print("3")
}
let row1 = TableRow<String, StoryboardImageTableViewCell>(item: "1")
let row2 = TableRow<String, StoryboardImageTableViewCell>(item: "2")
let row3 = TableRow<String, StoryboardImageTableViewCell>(item: "3", actions: [a])
row1
.action(TableRowAction(.click) { (row) in
print("1")
})
.action(TableRowAction(.click) { (row) -> String in
print("2")
return ""
})
let section = TableSection(headerTitle: "", footerTitle: "", rows: [row1, row2, row3])
tableDirector.append(section: section)
/*rowBuilder
.addAction(TableRowAction(type: .Click) { (data) in
})
rowBuilder
.delete(indexes: [0], animated: .None)
.insert(["2"], atIndex: 0, animated: .None)
.update(index: 0, item: "", animated: .None)
.move([1, 2], toIndexes: [3, 4])
//tableView.moveRowAtIndexPath(<#T##indexPath: NSIndexPath##NSIndexPath#>, toIndexPath: <#T##NSIndexPath#>)
//tableView.deleteRowsAtIndexPaths(<#T##indexPaths: [NSIndexPath]##[NSIndexPath]#>, withRowAnimation: <#T##UITableViewRowAnimation#>)
//tableView.insertRowsAtIndexPaths(<#T##indexPaths: [NSIndexPath]##[NSIndexPath]#>, withRowAnimation: <#T##UITableViewRowAnimation#>)
//tableView.reloadRowsAtIndexPaths(<#T##indexPaths: [NSIndexPath]##[NSIndexPath]#>, withRowAnimation: <#T##UITableViewRowAnimation#>)
//tableView.moveSection(0, toSection: 0)
//tableView.reloadSections([], withRowAnimation: .None)
//tableView.deleteSections([], withRowAnimation: .None)
//tableView.insertSections([], withRowAnimation: .None)
//tableDirector.performBatchUpdates {
//}*/
//tableDirector.append(section: section)
}
}

View File

@ -0,0 +1,33 @@
//
// StoryboardImageTableViewCell.swift
// TabletDemo
//
// Created by Max Sokolov on 24/05/16.
// Copyright © 2016 Tablet. All rights reserved.
//
import UIKit
import TableKit
class StoryboardImageTableViewCell: UITableViewCell, ConfigurableCell {
typealias T = String
@IBOutlet var titleLabel: UILabel!
@IBOutlet var subtitleLabel: UILabel!
@IBOutlet var customImageView: UIImageView!
func configure(string: T) {
titleLabel.text = string
subtitleLabel.text = "Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.1"
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.layoutIfNeeded()
subtitleLabel.preferredMaxLayoutWidth = subtitleLabel.bounds.size.width
}
}

View File

@ -0,0 +1,19 @@
//
// StoryboardTableViewCell.swift
// TabletDemo
//
// Created by Max Sokolov on 16/04/16.
// Copyright © 2016 Tablet. All rights reserved.
//
import UIKit
import TableKit
class StoryboardTableViewCell: UITableViewCell, ConfigurableCell {
typealias T = String
func configure(value: T) {
textLabel?.text = value
}
}

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.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>

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.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="dOU-ON-YYD"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="dOU-ON-YYD">
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
@ -16,17 +16,17 @@
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
<connections> <connections>
<segue destination="hPs-tQ-ZjY" kind="relationship" relationship="rootViewController" id="cVN-5m-Nvf"/> <segue destination="grv-aL-Qbb" kind="relationship" relationship="rootViewController" id="0SZ-hs-iUi"/>
</connections> </connections>
</navigationController> </navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="1px-T5-UXL" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="1px-T5-UXL" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="334" y="287"/> <point key="canvasLocation" x="334" y="287"/>
</scene> </scene>
<!--Autolayout Cells Controller--> <!--Main Controller-->
<scene sceneID="bgC-Xq-OSw"> <scene sceneID="bgC-Xq-OSw">
<objects> <objects>
<viewController id="grv-aL-Qbb" customClass="AutolayoutCellsController" customModule="TableKitDemo" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="grv-aL-Qbb" customClass="MainController" customModule="TabletDemo" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides> <layoutGuides>
<viewControllerLayoutGuide type="top" id="COn-EH-LKP"/> <viewControllerLayoutGuide type="top" id="COn-EH-LKP"/>
<viewControllerLayoutGuide type="bottom" id="iga-ib-rj1"/> <viewControllerLayoutGuide type="bottom" id="iga-ib-rj1"/>
@ -39,17 +39,17 @@
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes> <prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="AutolayoutTableViewCell" rowHeight="141" id="IBY-tW-SgU" customClass="AutolayoutTableViewCell" customModule="TableKitDemo" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="StoryboardImageTableViewCell" rowHeight="141" id="IBY-tW-SgU" customClass="StoryboardImageTableViewCell" customModule="TabletDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="92" width="600" height="141"/> <rect key="frame" x="0.0" y="92" width="600" height="141"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="IBY-tW-SgU" id="UNZ-nz-200"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="IBY-tW-SgU" id="UNZ-nz-200">
<rect key="frame" x="0.0" y="0.0" width="600" height="141"/> <rect key="frame" x="0.0" y="0.0" width="600" height="140"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="751" verticalHuggingPriority="751" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" placeholderIntrinsicWidth="172" placeholderIntrinsicHeight="21" text="Title" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YnK-4H-0SS"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="751" verticalHuggingPriority="751" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" placeholderIntrinsicWidth="172" placeholderIntrinsicHeight="21" text="Title" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YnK-4H-0SS">
<rect key="frame" x="128" y="20" width="452" height="21"/> <rect key="frame" x="128" y="20" width="452" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="750" placeholderIntrinsicWidth="172" placeholderIntrinsicHeight="18" text="Subtitle" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oPO-9F-UcX"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="750" placeholderIntrinsicWidth="172" placeholderIntrinsicHeight="18" text="Subtitle" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oPO-9F-UcX">
@ -60,16 +60,10 @@
</label> </label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="il6-0T-Sfb"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="il6-0T-Sfb">
<rect key="frame" x="20" y="20" width="100" height="100"/> <rect key="frame" x="20" y="20" width="100" height="100"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstAttribute="width" priority="999" constant="100" id="Ryh-cg-Q69"/> <constraint firstAttribute="width" priority="999" constant="100" id="Ryh-cg-Q69"/>
<constraint firstAttribute="height" priority="999" constant="100" id="cjb-Sw-psw"/> <constraint firstAttribute="height" priority="999" constant="100" id="cjb-Sw-psw"/>
</constraints> </constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="4"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</imageView> </imageView>
</subviews> </subviews>
<constraints> <constraints>
@ -86,10 +80,19 @@
</constraints> </constraints>
</tableViewCellContentView> </tableViewCellContentView>
<connections> <connections>
<outlet property="customImageView" destination="il6-0T-Sfb" id="Ufi-6Y-Vuf"/>
<outlet property="subtitleLabel" destination="oPO-9F-UcX" id="RIK-1t-nVt"/> <outlet property="subtitleLabel" destination="oPO-9F-UcX" id="RIK-1t-nVt"/>
<outlet property="titleLabel" destination="YnK-4H-0SS" id="ilA-7H-pq7"/> <outlet property="titleLabel" destination="YnK-4H-0SS" id="ilA-7H-pq7"/>
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="StoryboardTableViewCell" id="nE5-Y5-OFf" customClass="StoryboardTableViewCell" customModule="TabletDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="233" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="nE5-Y5-OFf" id="3yF-sl-yNq">
<rect key="frame" x="0.0" y="0.0" width="600" height="43"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes> </prototypes>
</tableView> </tableView>
</subviews> </subviews>
@ -104,65 +107,56 @@
<navigationItem key="navigationItem" id="HPV-jJ-NPc"/> <navigationItem key="navigationItem" id="HPV-jJ-NPc"/>
<connections> <connections>
<outlet property="tableView" destination="o67-xJ-fPW" id="A8B-MV-tNa"/> <outlet property="tableView" destination="o67-xJ-fPW" id="A8B-MV-tNa"/>
<segue destination="sSs-TX-Ch0" kind="show" identifier="headerfooter" id="Nbk-od-yC5"/>
</connections> </connections>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="xij-Hw-J33" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="xij-Hw-J33" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="1762" y="-59"/> <point key="canvasLocation" x="1042" y="287"/>
</scene> </scene>
<!--Main Controller--> <!--Header Footer Controller-->
<scene sceneID="rB2-pg-ya1"> <scene sceneID="Jd0-of-RF9">
<objects> <objects>
<viewController id="hPs-tQ-ZjY" customClass="MainController" customModule="TableKitDemo" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="sSs-TX-Ch0" customClass="HeaderFooterController" customModule="TabletDemo" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides> <layoutGuides>
<viewControllerLayoutGuide type="top" id="bj8-sc-pHV"/> <viewControllerLayoutGuide type="top" id="s9s-Gu-3M6"/>
<viewControllerLayoutGuide type="bottom" id="exD-V1-XKl"/> <viewControllerLayoutGuide type="bottom" id="Tux-up-dnH"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="w1B-sD-vvx"> <view key="view" contentMode="scaleToFill" id="CB5-10-J2D">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="lVS-GW-taC"> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="uLr-Ff-utK">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="StoryboardTableViewCell" id="lqW-2N-6mf" customClass="StoryboardTableViewCell" customModule="TabletDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="92" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="lqW-2N-6mf" id="tjN-ow-437">
<rect key="frame" x="0.0" y="0.0" width="600" height="43"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
</tableView> </tableView>
</subviews> </subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstItem="lVS-GW-taC" firstAttribute="leading" secondItem="w1B-sD-vvx" secondAttribute="leading" id="BML-us-tKv"/> <constraint firstItem="Tux-up-dnH" firstAttribute="top" secondItem="uLr-Ff-utK" secondAttribute="bottom" id="0jr-6S-PFB"/>
<constraint firstItem="lVS-GW-taC" firstAttribute="top" secondItem="w1B-sD-vvx" secondAttribute="top" id="KmO-4D-EEl"/> <constraint firstItem="uLr-Ff-utK" firstAttribute="top" secondItem="CB5-10-J2D" secondAttribute="top" id="KYu-01-hlH"/>
<constraint firstItem="exD-V1-XKl" firstAttribute="top" secondItem="lVS-GW-taC" secondAttribute="bottom" id="plx-uF-bQd"/> <constraint firstAttribute="trailing" secondItem="uLr-Ff-utK" secondAttribute="trailing" id="TKR-R1-lBY"/>
<constraint firstAttribute="trailing" secondItem="lVS-GW-taC" secondAttribute="trailing" id="uYe-yd-m86"/> <constraint firstItem="uLr-Ff-utK" firstAttribute="leading" secondItem="CB5-10-J2D" secondAttribute="leading" id="aG6-R3-QUr"/>
</constraints> </constraints>
</view> </view>
<navigationItem key="navigationItem" id="i9y-B7-dhJ"/> <navigationItem key="navigationItem" id="ogA-Id-clb"/>
<connections> <connections>
<outlet property="tableView" destination="lVS-GW-taC" id="C28-ml-ARU"/> <outlet property="tableView" destination="uLr-Ff-utK" id="olP-LV-VmT"/>
<segue destination="grv-aL-Qbb" kind="show" identifier="autolayoutcells" id="YrM-qz-6Kd"/>
<segue destination="apk-bL-6NO" kind="show" identifier="nibcells" id="gXD-XZ-oBm"/>
</connections> </connections>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="i96-Zo-Wfg" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="Gim-O8-I3d" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="1046" y="287"/> <point key="canvasLocation" x="1763" y="287"/>
</scene>
<!--Nib Cells Controller-->
<scene sceneID="DHa-3S-OkR">
<objects>
<tableViewController id="apk-bL-6NO" customClass="NibCellsController" customModule="TableKitDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="wDt-Cb-VB2">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<connections>
<outlet property="dataSource" destination="apk-bL-6NO" id="Xhe-U9-wKI"/>
<outlet property="delegate" destination="apk-bL-6NO" id="NqC-Vg-68j"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="oWG-DK-A9C" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1762" y="671"/>
</scene> </scene>
</scenes> </scenes>
</document> </document>

View File

@ -7,71 +7,20 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
5079ADE31FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5079ADE21FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift */; }; DA08A0531CF4E9B500BBF1F8 /* StoryboardImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA08A0521CF4E9B500BBF1F8 /* StoryboardImageTableViewCell.swift */; };
5079ADE61FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5079ADE51FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib */; }; DA9EA7821D0B6B070021F650 /* TableKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9EA7811D0B6B070021F650 /* TableKit.framework */; };
DA08A0531CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA08A0521CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift */; };
DA55465D1D1569CC00AA83EE /* AutolayoutCellsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA55465C1D1569CC00AA83EE /* AutolayoutCellsController.swift */; };
DA5546601D156A4F00AA83EE /* ConfigurableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA55465F1D156A4F00AA83EE /* ConfigurableTableViewCell.swift */; };
DA5546641D15762000AA83EE /* NibTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5546631D15762000AA83EE /* NibTableViewCell.swift */; };
DA5546661D15765900AA83EE /* NibTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA5546651D15765900AA83EE /* NibTableViewCell.xib */; };
DA5546681D15771D00AA83EE /* NibCellsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5546671D15771D00AA83EE /* NibCellsController.swift */; };
DA9EA7D91D0EC65B0021F650 /* TableKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9EA7D61D0EC5C60021F650 /* TableKit.framework */; };
DA9EA7DA1D0EC65B0021F650 /* TableKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA9EA7D61D0EC5C60021F650 /* TableKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
DAC2D5CA1C9D303E009E9C19 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC2D5C91C9D303E009E9C19 /* AppDelegate.swift */; }; DAC2D5CA1C9D303E009E9C19 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC2D5C91C9D303E009E9C19 /* AppDelegate.swift */; };
DAC2D5CF1C9D30A7009E9C19 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAC2D5CD1C9D30A7009E9C19 /* Main.storyboard */; }; DAC2D5CF1C9D30A7009E9C19 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAC2D5CD1C9D30A7009E9C19 /* Main.storyboard */; };
DAC2D5D01C9D30A7009E9C19 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAC2D5CE1C9D30A7009E9C19 /* LaunchScreen.storyboard */; }; DAC2D5D01C9D30A7009E9C19 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAC2D5CE1C9D30A7009E9C19 /* LaunchScreen.storyboard */; };
DAC2D69C1C9E75E3009E9C19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DAC2D69B1C9E75E3009E9C19 /* Assets.xcassets */; }; DAC2D69C1C9E75E3009E9C19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DAC2D69B1C9E75E3009E9C19 /* Assets.xcassets */; };
DACB71761CC2D63D00432BD3 /* MainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACB71751CC2D63D00432BD3 /* MainController.swift */; }; DACB71761CC2D63D00432BD3 /* MainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACB71751CC2D63D00432BD3 /* MainController.swift */; };
DACB71781CC2D6ED00432BD3 /* StoryboardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACB71771CC2D6ED00432BD3 /* StoryboardTableViewCell.swift */; };
DACB717A1CC2D89D00432BD3 /* HeaderFooterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACB71791CC2D89D00432BD3 /* HeaderFooterController.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
DA9EA7D51D0EC5C60021F650 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DA9EA7D01D0EC5C50021F650 /* TableKit.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = DA9EA7561D0B679A0021F650;
remoteInfo = TableKit;
};
DA9EA7D71D0EC5C60021F650 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DA9EA7D01D0EC5C50021F650 /* TableKit.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = DA9EA7C41D0EC45F0021F650;
remoteInfo = TableKitTests;
};
DA9EA7DB1D0EC65B0021F650 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DA9EA7D01D0EC5C50021F650 /* TableKit.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = DA9EA7551D0B679A0021F650;
remoteInfo = TableKit;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
DA9EA7DD1D0EC65B0021F650 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
DA9EA7DA1D0EC65B0021F650 /* TableKit.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
5079ADE21FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutolayoutSectionHeaderView.swift; sourceTree = "<group>"; }; DA08A0521CF4E9B500BBF1F8 /* StoryboardImageTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardImageTableViewCell.swift; sourceTree = "<group>"; };
5079ADE51FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AutolayoutSectionHeaderView.xib; sourceTree = "<group>"; }; DA9EA7811D0B6B070021F650 /* TableKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TableKit.framework; path = "../../../../../Library/Developer/Xcode/DerivedData/TableKit-blgxvmkyvpocltgadmpliruugomo/Build/Products/Debug-iphonesimulator/TableKit.framework"; sourceTree = "<group>"; };
DA08A0521CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutolayoutTableViewCell.swift; sourceTree = "<group>"; };
DA55465C1D1569CC00AA83EE /* AutolayoutCellsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutolayoutCellsController.swift; sourceTree = "<group>"; };
DA55465F1D156A4F00AA83EE /* ConfigurableTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableTableViewCell.swift; sourceTree = "<group>"; };
DA5546631D15762000AA83EE /* NibTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibTableViewCell.swift; sourceTree = "<group>"; };
DA5546651D15765900AA83EE /* NibTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NibTableViewCell.xib; sourceTree = "<group>"; };
DA5546671D15771D00AA83EE /* NibCellsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibCellsController.swift; sourceTree = "<group>"; };
DA9EA7D01D0EC5C50021F650 /* TableKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = TableKit.xcodeproj; path = ../TableKit.xcodeproj; sourceTree = "<group>"; };
DAB7EB271BEF787300D2AD5E /* TableKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TableKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; DAB7EB271BEF787300D2AD5E /* TableKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TableKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
DAC2D5C91C9D303E009E9C19 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; DAC2D5C91C9D303E009E9C19 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
DAC2D5CD1C9D30A7009E9C19 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; }; DAC2D5CD1C9D30A7009E9C19 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
@ -79,6 +28,8 @@
DAC2D69B1C9E75E3009E9C19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; DAC2D69B1C9E75E3009E9C19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
DAC2D69D1C9E78B5009E9C19 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; DAC2D69D1C9E78B5009E9C19 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DACB71751CC2D63D00432BD3 /* MainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainController.swift; sourceTree = "<group>"; }; DACB71751CC2D63D00432BD3 /* MainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainController.swift; sourceTree = "<group>"; };
DACB71771CC2D6ED00432BD3 /* StoryboardTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardTableViewCell.swift; sourceTree = "<group>"; };
DACB71791CC2D89D00432BD3 /* HeaderFooterController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFooterController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -86,7 +37,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
DA9EA7D91D0EC65B0021F650 /* TableKit.framework in Frameworks */, DA9EA7821D0B6B070021F650 /* TableKit.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -96,23 +47,14 @@
DA539C871CF50B1800368ACB /* Frameworks */ = { DA539C871CF50B1800368ACB /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA9EA7811D0B6B070021F650 /* TableKit.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DA9EA7D11D0EC5C50021F650 /* Products */ = {
isa = PBXGroup;
children = (
DA9EA7D61D0EC5C60021F650 /* TableKit.framework */,
DA9EA7D81D0EC5C60021F650 /* TableKitTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
DAB7EB1E1BEF787300D2AD5E = { DAB7EB1E1BEF787300D2AD5E = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA9EA7D01D0EC5C50021F650 /* TableKit.xcodeproj */,
DAC2D5C61C9D2FE5009E9C19 /* Classes */, DAC2D5C61C9D2FE5009E9C19 /* Classes */,
DAC2D5CB1C9D3058009E9C19 /* Resources */, DAC2D5CB1C9D3058009E9C19 /* Resources */,
DA539C871CF50B1800368ACB /* Frameworks */, DA539C871CF50B1800368ACB /* Frameworks */,
@ -185,8 +127,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DACB71751CC2D63D00432BD3 /* MainController.swift */, DACB71751CC2D63D00432BD3 /* MainController.swift */,
DA55465C1D1569CC00AA83EE /* AutolayoutCellsController.swift */, DACB71791CC2D89D00432BD3 /* HeaderFooterController.swift */,
DA5546671D15771D00AA83EE /* NibCellsController.swift */,
); );
path = Controllers; path = Controllers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -194,12 +135,8 @@
DACB71741CC2D5FD00432BD3 /* Views */ = { DACB71741CC2D5FD00432BD3 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA55465F1D156A4F00AA83EE /* ConfigurableTableViewCell.swift */, DACB71771CC2D6ED00432BD3 /* StoryboardTableViewCell.swift */,
DA08A0521CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift */, DA08A0521CF4E9B500BBF1F8 /* StoryboardImageTableViewCell.swift */,
DA5546631D15762000AA83EE /* NibTableViewCell.swift */,
DA5546651D15765900AA83EE /* NibTableViewCell.xib */,
5079ADE21FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift */,
5079ADE51FE1BF65000CC345 /* AutolayoutSectionHeaderView.xib */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@ -214,12 +151,10 @@
DAB7EB231BEF787300D2AD5E /* Sources */, DAB7EB231BEF787300D2AD5E /* Sources */,
DAB7EB241BEF787300D2AD5E /* Frameworks */, DAB7EB241BEF787300D2AD5E /* Frameworks */,
DAB7EB251BEF787300D2AD5E /* Resources */, DAB7EB251BEF787300D2AD5E /* Resources */,
DA9EA7DD1D0EC65B0021F650 /* Embed Frameworks */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
DA9EA7DC1D0EC65B0021F650 /* PBXTargetDependency */,
); );
name = TableKitDemo; name = TableKitDemo;
productName = TabletDemo; productName = TabletDemo;
@ -233,13 +168,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,19 +182,12 @@
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
Base, Base,
); );
mainGroup = DAB7EB1E1BEF787300D2AD5E; mainGroup = DAB7EB1E1BEF787300D2AD5E;
productRefGroup = DAB7EB281BEF787300D2AD5E /* Products */; productRefGroup = DAB7EB281BEF787300D2AD5E /* Products */;
projectDirPath = ""; projectDirPath = "";
projectReferences = (
{
ProductGroup = DA9EA7D11D0EC5C50021F650 /* Products */;
ProjectRef = DA9EA7D01D0EC5C50021F650 /* TableKit.xcodeproj */;
},
);
projectRoot = ""; projectRoot = "";
targets = ( targets = (
DAB7EB261BEF787300D2AD5E /* TableKitDemo */, DAB7EB261BEF787300D2AD5E /* TableKitDemo */,
@ -268,31 +195,12 @@
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXReferenceProxy section */
DA9EA7D61D0EC5C60021F650 /* TableKit.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = TableKit.framework;
remoteRef = DA9EA7D51D0EC5C60021F650 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
DA9EA7D81D0EC5C60021F650 /* TableKitTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = TableKitTests.xctest;
remoteRef = DA9EA7D71D0EC5C60021F650 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
DAB7EB251BEF787300D2AD5E /* Resources */ = { DAB7EB251BEF787300D2AD5E /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
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 */,
DAC2D5D01C9D30A7009E9C19 /* LaunchScreen.storyboard in Resources */, DAC2D5D01C9D30A7009E9C19 /* LaunchScreen.storyboard in Resources */,
DAC2D69C1C9E75E3009E9C19 /* Assets.xcassets in Resources */, DAC2D69C1C9E75E3009E9C19 /* Assets.xcassets in Resources */,
); );
@ -305,27 +213,16 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
DACB71781CC2D6ED00432BD3 /* StoryboardTableViewCell.swift in Sources */,
DACB71761CC2D63D00432BD3 /* MainController.swift in Sources */, DACB71761CC2D63D00432BD3 /* MainController.swift in Sources */,
DA55465D1D1569CC00AA83EE /* AutolayoutCellsController.swift in Sources */,
DA5546681D15771D00AA83EE /* NibCellsController.swift in Sources */,
DAC2D5CA1C9D303E009E9C19 /* AppDelegate.swift in Sources */, DAC2D5CA1C9D303E009E9C19 /* AppDelegate.swift in Sources */,
5079ADE31FE1BF1B000CC345 /* AutolayoutSectionHeaderView.swift in Sources */, DACB717A1CC2D89D00432BD3 /* HeaderFooterController.swift in Sources */,
DA5546641D15762000AA83EE /* NibTableViewCell.swift in Sources */, DA08A0531CF4E9B500BBF1F8 /* StoryboardImageTableViewCell.swift in Sources */,
DA5546601D156A4F00AA83EE /* ConfigurableTableViewCell.swift in Sources */,
DA08A0531CF4E9B500BBF1F8 /* AutolayoutTableViewCell.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
DA9EA7DC1D0EC65B0021F650 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = TableKit;
targetProxy = DA9EA7DB1D0EC65B0021F650 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
DAB7EB371BEF787300D2AD5E /* Debug */ = { DAB7EB371BEF787300D2AD5E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
@ -335,23 +232,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 +265,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 +276,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 +301,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,7 +308,6 @@
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";
@ -444,14 +317,12 @@
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";
@ -461,7 +332,6 @@
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,245 +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 XCTest
import TableKit
class TestController: UITableViewController {
var tableDirector: TableDirector!
override func viewDidLoad() {
super.viewDidLoad()
tableDirector = TableDirector(tableView: tableView)
}
}
struct TestData {
let title: String
}
struct TestTableViewCellOptions {
static let ReusableIdentifier: String = "ReusableIdentifier"
static let CellAction: String = "CellAction"
static let CellActionUserInfoKey: String = "CellActionUserInfoKey"
static let CellActionUserInfoValue: String = "CellActionUserInfoValue"
static let EstimatedHeight: CGFloat = 255
}
class TestTableViewCell: UITableViewCell, ConfigurableCell {
typealias T = TestData
static var estimatedHeight: CGFloat? {
return TestTableViewCellOptions.EstimatedHeight
}
static var reuseIdentifier: String {
return TestTableViewCellOptions.ReusableIdentifier
}
func configure(with item: T) {
textLabel?.text = item.title
}
func raiseAction() {
TableCellAction(key: TestTableViewCellOptions.CellAction, sender: self, userInfo: nil).invoke()
}
}
class TableKitTests: XCTestCase {
var testController: TestController!
override func setUp() {
super.setUp()
testController = TestController()
testController.tableView.frame = UIScreen.main.bounds
testController.tableView.isHidden = false
testController.tableView.setNeedsLayout()
testController.tableView.layoutIfNeeded()
}
override func tearDown() {
testController = nil
super.tearDown()
}
func testTableDirectorHasTableView() {
XCTAssertNotNil(testController.tableView, "TestController should have table view")
XCTAssertNotNil(testController.tableDirector, "TestController should have table director")
XCTAssertNotNil(testController.tableDirector.tableView, "TableDirector should have table view")
}
func testRowInSection() {
let data = TestData(title: "title")
let row = TableRow<TestTableViewCell>(item: data)
testController.tableDirector += row
testController.tableView.reloadData()
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section")
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
XCTAssertNotNil(cell)
XCTAssertTrue(cell?.textLabel?.text == data.title)
}
func testManyRowsInSection() {
let data = [TestData(title: "1"), TestData(title: "2"), TestData(title: "3")]
let rows: [Row] = data.map({ TableRow<TestTableViewCell>(item: $0) })
testController.tableDirector += rows
testController.tableView.reloadData()
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == data.count, "Table view should have certain number of rows in a section")
for (index, element) in data.enumerated() {
let cell = testController.tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? TestTableViewCell
XCTAssertNotNil(cell)
XCTAssertTrue(cell?.textLabel?.text == element.title)
}
}
func testTableSectionCreatesSectionWithHeaderAndFooterTitles() {
let row = TableRow<TestTableViewCell>(item: TestData(title: "title"))
let sectionHeaderTitle = "Header Title"
let sectionFooterTitle = "Footer Title"
let section = TableSection(headerTitle: sectionHeaderTitle, footerTitle: sectionFooterTitle, rows: [row])
testController.tableDirector += section
testController.tableView.reloadData()
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView?(testController.tableView, titleForHeaderInSection: 0) == sectionHeaderTitle)
XCTAssertTrue(testController.tableView.dataSource?.tableView?(testController.tableView, titleForFooterInSection: 0) == sectionFooterTitle)
}
func testTableSectionCreatesSectionWithHeaderAndFooterViews() {
let row = TableRow<TestTableViewCell>(item: TestData(title: "title"))
let sectionHeaderView = UIView()
let sectionFooterView = UIView()
let section = TableSection(headerView: sectionHeaderView, footerView: sectionFooterView, rows: nil)
section += row
testController.tableDirector += section
testController.tableView.reloadData()
XCTAssertTrue(testController.tableView.dataSource?.numberOfSections?(in: testController.tableView) == 1, "Table view should have a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section")
XCTAssertTrue(testController.tableView.delegate?.tableView?(testController.tableView, viewForHeaderInSection: 0) == sectionHeaderView)
XCTAssertTrue(testController.tableView.delegate?.tableView?(testController.tableView, viewForFooterInSection: 0) == sectionFooterView)
}
func testRowBuilderCustomActionInvokedAndSentUserInfo() {
let expectation = self.expectation(description: "cell action")
let row = TableRow<TestTableViewCell>(item: TestData(title: "title"))
.on(TableRowAction(.custom(TestTableViewCellOptions.CellAction)) { (data) in
XCTAssertNotNil(data.cell, "Action data should have a cell")
expectation.fulfill()
})
testController.view.isHidden = false
testController.tableDirector += row
testController.tableView.reloadData()
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
XCTAssertNotNil(cell, "Cell should exists and should be TestTableViewCell")
cell?.raiseAction()
waitForExpectations(timeout: 1.0, handler: nil)
}
func testReplaceSectionOnExistingIndex() {
let row1 = TableRow<TestTableViewCell>(item: TestData(title: "title1"))
let row2 = TableRow<TestTableViewCell>(item: TestData(title: "title2"))
let section1 = TableSection(headerView: nil, footerView: nil, rows: nil)
section1 += row1
let section2 = TableSection(headerView: nil, footerView: nil, rows: nil)
section2 += row2
testController.tableDirector += section1
testController.tableView.reloadData()
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
XCTAssertTrue(cell?.textLabel?.text == "title1")
testController.tableDirector.replaceSection(at: 0, with: section2)
testController.tableView.reloadData()
let cell1 = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
XCTAssertTrue(cell1?.textLabel?.text == "title2")
}
func testReplaceSectionOnWrongIndex() {
let row1 = TableRow<TestTableViewCell>(item: TestData(title: "title1"))
let row2 = TableRow<TestTableViewCell>(item: TestData(title: "title2"))
let section1 = TableSection(headerView: nil, footerView: nil, rows: nil)
section1 += row1
let section2 = TableSection(headerView: nil, footerView: nil, rows: nil)
section2 += row2
testController.tableDirector += section1
testController.tableView.reloadData()
let cell = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
XCTAssertTrue(cell?.textLabel?.text == "title1")
testController.tableDirector.replaceSection(at: 33, with: section2)
testController.tableView.reloadData()
let cell1 = testController.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? TestTableViewCell
XCTAssertTrue(cell1?.textLabel?.text == "title1")
}
}

200
Tests/TabletTests.swift Normal file
View File

@ -0,0 +1,200 @@
//
// 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 XCTest
import Tablet
class TestController: UITableViewController {
var tableDirector: TableDirector!
override func viewDidLoad() {
super.viewDidLoad()
tableDirector = TableDirector(tableView: tableView)
}
}
struct TestData {
let title: String
}
struct TestTableViewCellOptions {
static let ReusableIdentifier: String = "ReusableIdentifier"
static let CellAction: String = "CellAction"
static let CellActionUserInfoKey: String = "CellActionUserInfoKey"
static let CellActionUserInfoValue: String = "CellActionUserInfoValue"
static let EstimatedHeight: Float = 255
}
class TestTableViewCell: UITableViewCell, ConfigurableCell {
typealias T = TestData
static func reusableIdentifier() -> String {
return TestTableViewCellOptions.ReusableIdentifier
}
static func estimatedHeight() -> Float {
return TestTableViewCellOptions.EstimatedHeight
}
func configure(item: T) {
textLabel?.text = item.title
}
func raiseAction() {
Action(key: TestTableViewCellOptions.CellAction, sender: self, userInfo: [TestTableViewCellOptions.CellActionUserInfoKey: TestTableViewCellOptions.CellActionUserInfoValue]).invoke()
}
}
class TabletTests: XCTestCase {
var testController: TestController!
override func setUp() {
super.setUp()
testController = TestController()
}
override func tearDown() {
testController = nil
super.tearDown()
}
func testTableDirectorHasTableView() {
XCTAssertNotNil(testController.tableView, "TestController should have table view")
XCTAssertNotNil(testController.tableDirector, "TestController should have table director")
XCTAssertNotNil(testController.tableDirector.tableView, "TableDirector should have table view")
}
func testSimpleRowBuilderCreatesRowsAndSection() {
let source = ["1", "2", "3"]
let rows = TableBaseRowBuilder<String, UITableViewCell>(items: source)
.action(.configure) { data -> Void in
XCTAssertNotNil(data.cell, "Action should have a cell")
data.cell?.textLabel?.text = "\(data.item)"
}
testController.view.hidden = false
testController.tableDirector += rows
testController.tableView.reloadData()
XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section")
XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == source.count, "Table view should have certain number of rows in a section")
for (index, element) in source.enumerate() {
let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0))
XCTAssertNotNil(cell)
XCTAssertTrue(cell?.textLabel?.text == element)
}
}
func testConfigurableRowBuilderCreatesRowsAndSection() {
let testData = TestData(title: "title")
testController.view.hidden = false
testController.tableDirector += TableRowBuilder<TestData, TestTableViewCell>(item: testData)
testController.tableView.reloadData()
let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell
XCTAssertNotNil(cell, "Cell should exists and should be TestTableViewCell")
XCTAssertTrue(cell?.textLabel?.text == testData.title, "Cell's textLabel.text should equal to testData's title")
}
func testSectionBuilderCreatesSectionWithHeaderAndFooterTitles() {
let row = TableRowBuilder<TestData, TestTableViewCell>(items: [TestData(title: "title")])
let sectionHeaderTitle = "Header Title"
let sectionFooterTitle = "Footer Title"
let section = TableSectionBuilder(headerTitle: sectionHeaderTitle, footerTitle: sectionFooterTitle, rows: [row])
testController.view.hidden = false
testController.tableDirector += section
testController.tableView.reloadData()
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, titleForHeaderInSection: 0) == sectionHeaderTitle)
XCTAssertTrue(testController.tableView.dataSource?.tableView?(testController.tableView, titleForFooterInSection: 0) == sectionFooterTitle)
}
func testSectionBuilderCreatesSectionWithHeaderAndFooterViews() {
let row = TableRowBuilder<TestData, TestTableViewCell>(items: [TestData(title: "title")])
let sectionHeaderView = UIView()
let sectionFooterView = UIView()
let section = TableSectionBuilder(headerView: sectionHeaderView, footerView: sectionFooterView, rows: nil)
section += row
testController.view.hidden = false
testController.tableDirector += section
testController.tableView.reloadData()
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.delegate?.tableView?(testController.tableView, viewForHeaderInSection: 0) == sectionHeaderView)
XCTAssertTrue(testController.tableView.delegate?.tableView?(testController.tableView, viewForFooterInSection: 0) == sectionFooterView)
}
func testRowBuilderCustomActionInvokedAndSentUserInfo() {
let expectation = expectationWithDescription("cell action")
let row = TableRowBuilder<TestData, TestTableViewCell>(items: [TestData(title: "title")])
.action(TestTableViewCellOptions.CellAction) { data -> Void in
XCTAssertNotNil(data.cell, "Action data should have a cell")
XCTAssertNotNil(data.userInfo, "Action data should have a user info dictionary")
XCTAssertTrue(data.userInfo?[TestTableViewCellOptions.CellActionUserInfoKey] as? String == TestTableViewCellOptions.CellActionUserInfoValue, "UserInfo should have correct value for key")
expectation.fulfill()
}
testController.view.hidden = false
testController.tableDirector += row
testController.tableView.reloadData()
let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell
XCTAssertNotNil(cell, "Cell should exists and should be TestTableViewCell")
cell?.raiseAction()
waitForExpectationsWithTimeout(1.0, handler: nil)
}
}