Type-safe declarative table views.

Overview

TableKit

Build Status Swift 5.1 compatible Carthage compatible CocoaPods compatible Platform iOS License: MIT

TableKit is a super lightweight yet powerful generic library that allows you to build complex table views in a declarative type-safe manner. It hides a complexity of UITableViewDataSource and UITableViewDelegate methods behind the scene, so your code will be look clean, easy to read and nice to maintain.

Features

  • Type-safe generic cells
  • Functional programming style friendly
  • The easiest way to map your models or view models to cells
  • Automatic cell registration*
  • Correctly handles autolayout cells with multiline labels
  • Chainable cell actions (select/deselect etc.)
  • Support cells created from code, xib, or storyboard
  • Support different cells height calculation strategies
  • Support portrait and landscape orientations
  • No need to subclass
  • Extensibility

Getting Started

An example app is included demonstrating TableKit's functionality.

Basic usage

Create your rows:

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:

let section = TableSection(rows: [row1, row2, row3])

And setup your table:

let tableDirector = TableDirector(tableView: tableView)
tableDirector += section

Done. Your table is ready. Your cells have to conform to ConfigurableCell protocol:

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:

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:

let row = TableRow<StringTableViewCell>(item: "some")
    .on(.click) { (options) in
	
    }
    .on(.shouldHighlight) { (options) -> Bool in
        return false
    }

You could find all available actions here.

Custom row actions

You are able to define your own actions:

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:

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:

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:

row.removeAction(forActionId: "action_id")

Advanced

Cell height calculating strategy

By default TableKit relies on self-sizing cells. In that case you have to provide an estimated height for your cells:

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:

let tableDirector = TableDirector(tableView: tableView, shouldUsePrototypeCellHeightCalculation: true)

It does all dirty work with prototypes for you behind the scene, so you don't have to worry about anything except of your cell configuration:

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.

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:

MyTableViewCell.swift
MyTableViewCell.xib

You can also turn off this behaviour:

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:

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.

License

TableKit is available under the MIT license. See LICENSE for details.

Comments
  • Can't remove top extra space for grouped tableview

    Can't remove top extra space for grouped tableview

    Hi.

    I have faced for strange behaviour of grouped tableview with TableKit. I can't remove extra top space for grouped table (I use xibs - iOS 10/11).

    I do following:

        func reload(withModels models: [MyModel]) {
    
            tableDirector.clear()
    
            let section = TableSection()
            tableDirector += section
    
            section.headerHeight = CGFloat.leastNormalMagnitude
            section.footerHeight = CGFloat.leastNormalMagnitude
    
            for item in models {
    
                section += TableRow<MyTableViewCell>(item: item)
                        .on(.click, handler: { [weak viewOutput] _ in
    
                        })
            }
    
            tableDirector.reload()
        }
    

    Also I have tried to make subclass of TableDirector and always return CGFloat.leastNormalMagnitude for all sections. It's didn't work also.

    --- But ---- If I implement datasource/delegate manually. It works fine - no any extra space on top.

    
    extension MyTableDataSource: UITableViewDelegate {
        func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            return MyTableViewCell.defaultHeight
        }
    
        func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
            return CGFloat.leastNormalMagnitude
        }
    
        func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
            return CGFloat.leastNormalMagnitude
        }
    }
    
    
    help wanted 
    opened by SergeMaslyakov 8
  • Append to section - memory leak

    Append to section - memory leak

    @maxsokolov Привет! Во-первых Макс спасибо за отличную библиотеку! Но похоже есть небольшой memory leak, посмотри:

    In English: I have very strange memory leak, it appears when i try append row in sections.

    func reloadData() {
            _ = tableDirector.clear()
            // Row number for selected city (for scrolling)
            var selectedCityRowNumber = 0
            
            if let cityList = cityList {
                for city in cityList {
                    let isSelected = (selectedCityId == city.id) ? true : false
                    let row = TableRow<SelectDepartureCityScreenTableViewCell>(item: (city, isSelected)).on(.click) { [weak self] data in
                        
                        // Handler on cell clicking
                        self?.output.didTapDepartureCity(withId: data.item.city.id)
                    }
                    section.append(row: row)
                    
                    if isSelected {
                        selectedCityRowNumber = section.numberOfRows
                    }
                }
                
                tableDirector += section
                
                // Scroll to selected number
                let selectedPath = IndexPath(row: selectedCityRowNumber, section: 0)
                tableDirector.tableView?.scrollToRow(at: selectedPath, at: .middle, animated: false)
            }
        }
    }
    
    

    на section.append вылазит memory leak: screenshot at aug 16 02-16-48

    Как не пробовал, не могу от него избавится, есть идеи? Чуть чуть другой текст вылазит если без создания section'a делать например tableDirector += row Или отдельно создавать TableRowAction , и потом заносить его в конструктор TableRow, утечка остается просто текст утечки немного меняется

    opened by denisenkoaj 8
  • Missing header view using AutoLayout

    Missing header view using AutoLayout

    I've created a UITableViewHeaderFooterView subclass, with just a simple label for now to test things out. I've pinned the label to the edges using constraints (all in code). Then, when creating my sections in my view controller, I create a new header view, configure it with the data it needs to set some text in the label and use that as the header view in the section initializer. However, when I run the app, the header doesn't show up. If I add break points, I can see that the view is created and the correct text is set, it just isn't visible... Do I need to manually specify the height of the header? It can't use AutoLayout to figure that out for me?

    opened by danmartyn 6
  • Updated Swift tools version and added missing requirements

    Updated Swift tools version and added missing requirements

    I've been using TableKit in all my projects with CocoaPods (thank you!) but recently tried to use it in a new project with SwiftPM in Xcode 11 beta 7 and it wouldn't work.

    I'm still getting this error and don't know how to fix it (the error message says the requirement it can't match is 2.9.0..<3.0.0, which makes no sense because there is a 2.9.0 tag and Xcode is recognising that as the latest version automatically). I've tried using version 2.8.0 as well, but get the same error.

    However, with the changes I've made to Package.swift in this PR, Xcode will add the package if I use the hash of the last commit. So I think these changes did need to be made to update the package file, but there's an outstanding issue with tags that I'm not sure about.

    opened by bellebethcooper 5
  • Add support for sending custom actions from cells

    Add support for sending custom actions from cells

    So we don't have to use delegate approach for cells.

    Cell:

    class StringTableViewCell: UITableViewCell, ConfigurableCell {
    
      @IBAction func sendTapped(sender: UIButton) {
        invokeCustomAction("send")
      }
    
    }
    

    Action in controller:

    let action = TableRowAction<String, StringTableViewCell>(.custom("send")) { (data) in
      doSomeNetworking()
    }
    

    Maybe it is stupid idea, but seems reasonable to me. Let me know what you think.

    opened by zdnk 5
  • [QUESTION] Scrolling is relatively glitchy

    [QUESTION] Scrolling is relatively glitchy

    I noticed that while scrolling through a UIView with a lot of cells, the line seperators flicker and it doesn't look to smooth. Would it be possible to make scrolling smoother?

    question 
    opened by avi2k4 4
  • willDisplayHeaderView and willDisplayFooterView

    willDisplayHeaderView and willDisplayFooterView

    Hi, I noticed that there is no way to get callbacks for willDisplayHeaderView and willDisplayFooterView. Is there any plans to add this and/or suggestion on how I can access those?

    Thanks, Sushant

    question 
    opened by sushant-is-here 3
  • [ВОПРОС] Как перезагружать секции при постраничной загрузке

    [ВОПРОС] Как перезагружать секции при постраничной загрузке

    Привет, Макс! Спасибо за твою работу. Подскажи, как сделать перезагрузки секций при постраничной загрузке. Когда делаю: tableDirector.clear() .... add section .... add rows

    tableDirector += section
    tableDirector.reload()
    

    То таблица скроллится в начало.

    question 
    opened by tmbiOS 3
  • Improve Generic parameters

    Improve Generic parameters

    Hi there In #42 where an interesting improvement by reusing CellType.ItemType generic. Would you add this one into repo? Can I help you with PR for that? May be @noxt want to make it first?

    question 
    opened by OdNairy 3
  • Row update and reorder animation

    Row update and reorder animation

    Hi, you created great way for work with UITableView, but I have some questions: How update some data in row with animation? How reorder row with animation?

    Thanks

    question 
    opened by AnisovAleksey 3
  • Table Sections - How to customize them?

    Table Sections - How to customize them?

    Hi! Congratulations for the library, really cool and useful! I need to create custom section cells, but the question is : how can I do it? I see the TableSection has a headerView property, but I'd like to know how can I customize the info inside my custom section cell and how can I reuse the section header cell.

    question 
    opened by andrealufino 3
  • How to activate canMove, canMoveTo for sorting drag & drop rows

    How to activate canMove, canMoveTo for sorting drag & drop rows

    Please tell me how to activate the ability to sort cells or sections in a table using TableKit

    Do not work:

    let row = TableRow<BudgetSettingsTableViewCell>(item: item)
    
    let canMove = TableRowAction<BudgetSettingsTableViewCell>(.canMove)  { options in
                    return true
    }
                
            
    let caneMoveTo = TableRowAction<BudgetSettingsTableViewCell>(.canMoveTo) { [weak self] (options) in
                    
               print("MOVE")
     }
    row.on(canMove)
    row.on(caneMoveTo)
    
    opened by Datastore24Kirill 0
  • Подскажи пожалуйста, как организовать получение данных из ячеек

    Подскажи пожалуйста, как организовать получение данных из ячеек

    Задачка: организовать получение данных из ячеек с сохранением типизации и без тайп-кастинга. Пример прикладной задачи: Есть n ячеек с разными полями ввода и интерактива с пользователем. В необходимый момент времени, нужно собрать данные из ячеек и сохранить в БД.

    opened by NeonGloss 7
  • Context menu improvements

    Context menu improvements

    Hi! I really like your framework and I found a case where it doesn't quite work. I use it for creating chat and I needed to create a context menu for the message bubble. But in this version of the framework this cannot be done. I add some methods from UITableViewDelegate to select view from cell which will be used by the context menu.

    Added methods from UITableViewDelegate:

    • previewForHighlightingContextMenuWithConfiguration
    • previewForDismissingContextMenuWithConfiguration

    Some code example how to use new functionality:

    let row = TableRow<MessageTableViewCell>(item: message)
      .on(.showContextMenu) { item -> UIContextMenuConfiguration in
          
          // Here you need to path indexPath to identifier.
          return UIContextMenuConfiguration(identifier: item.indexPath as NSIndexPath, previewProvider: nil) { _ in
              
              let copyAction = UIAction(title: R.string.chat.copy(), image: R.image.messageCopy())
              let deleteAction = UIAction(title: R.string.chat.delete(), image: R.image.messageDelete(), attributes: .destructive) 
              return UIMenu(children: [copyAction, deleteAction])
          }
      }
      // !New methods!
      .on(.previewForHighlightingContextMenu) { item -> UITargetedPreview in
          
          // Choosing view to use in contextMenu
          let view = cell.messageBackgroundView ?? UIView()
      
          // Choosing parameters
          let parameters = UIPreviewParameters()
          parameters.backgroundColor = .clear
          parameters.visiblePath = UIBezierPath(roundedRect: view.bounds, cornerRadius: 10))
      
          return UITargetedPreview(view: view, parameters: parameters)
      }
      .on(.previewForDismissingContextMenu) { item -> UITargetedPreview in
          
          let view = cell.messageBackgroundView ?? UIView()
          
          // Choosing parameters
          let parameters = UIPreviewParameters()
          parameters.backgroundColor = .clear
          parameters.visiblePath = UIBezierPath(roundedRect: view.bounds, cornerRadius: 10))
      
          return UITargetedPreview(view: view, parameters: parameters)
      }
    
    opened by ddanilyuk 0
  • Replace UITableViewRowAction with UIContextualAction, up iphoneos_deployment_target to 9.0

    Replace UITableViewRowAction with UIContextualAction, up iphoneos_deployment_target to 9.0

    Updated:

    • UITablewViewRowAction is deprecated. Replace it with UIContextualAction
    • Up IPHONEOS_DEPLOYMENT_TARGET to 9.0 with new Xcode
    • Update .gitignore for SPM
    opened by Loupehope 0
  • Dynamic section header and footer views

    Dynamic section header and footer views

    Hey! I made this change in my fork because I had a large dataset with many sections. Creating sections and their header views at once caused a performance and memory problem, so I added the ability to have a callback to generate the header/footer when needed. This also allows the client to cache header/footer views and reuse them.

    opened by mrojas 0
Releases(2.11.0)
Owner
Max Sokolov
Max Sokolov
This framework allows you to build Table views using UIKit with syntax similar to SwiftUI

This framework allows you to build Table views using UIKit with syntax similar to SwiftUI

Fun 60 Dec 17, 2022
Use Yelp API to fetch restuarants around a location and show them in a table view

Yelp Use Yelp API to fetch restuarants around a location and show them in a table view - Infinite scrolling, Prefetching, Image Caching. Design Patter

null 0 Nov 1, 2021
Using UI Table View

News-App Table View와 Table view controller Table View : Table의 크기를 지정할 수 있다. Table View Controller: 전체의 뷰가 하나의 테이블 Table View Table view 구성요소 결정하기 어떤

Jiwon 0 Dec 9, 2021
Typed, yet Flexible Table View Controller

ConfigurableTableViewController Simple view controller that provides a way to configure a table view with multiple types of cells while keeping type s

Arek Holko 270 Oct 15, 2022
A Swift library for swipeable table cells

BWSwipeRevealCell Using the library **Note: Use version 1.0.1 for Swift 2.3 support and version 2.0.0 or higher for Swift 3 support ** There are two m

Kyle Newsome 67 May 11, 2022
Generic table view controller with external data processing

FlexibleTableViewController Swift library of generic table view controller with external data processing of functionality, like determine cell's reuse

Dmytro Pylypenko 9 May 20, 2018
A UITableView extension that enables cell insertion from the bottom of a table view.

ReverseExtension UITableView extension that enabled to insert cell from bottom of tableView. Concept It is difficult to fill a tableview content from

Taiki Suzuki 1.7k Dec 15, 2022
TableViews - Emoji Table View For iOS With Swift

TableViews Hello! This is EmojiTableView. Let me introduce you my first app when

null 0 Jan 2, 2022
A declarative api for working with UITableView.

⚡️ Lightning Table Lightning Table provides a powerful declarative API for working with UITableView's. Table views are the foundation of almost every

Electric Kangaroo 28 Aug 25, 2021
A declarative wrapper approach to UITableView

Thunder Table Thunder Table is a useful framework which enables quick and easy creation of table views in iOS, making the process of creating complex

3 SIDED CUBE 20 Aug 25, 2022
Elegant and easy way to integrate pagination with dummy views

AZTableView Controller Features Automatic pagination handling No more awkward empty TableView screen AZ TableView controller give you advantage to con

Afroz Zaheer 73 Oct 17, 2022
Easy and type-safe iOS table and collection views in Swift.

Quick Start TL;DR? SimpleSource is a library that lets you populate and update table views and collection views with ease. It gives you fully typed cl

Squarespace 96 Dec 26, 2022
Easy and type-safe iOS table and collection views in Swift.

Quick Start TL;DR? SimpleSource is a library that lets you populate and update table views and collection views with ease. It gives you fully typed cl

Squarespace 96 Dec 26, 2022
Type-safe networking abstraction layer that associates request type with response type.

APIKit APIKit is a type-safe networking abstraction layer that associates request type with response type. // SearchRepositoriesRequest conforms to Re

Yosuke Ishikawa 1.9k Dec 30, 2022
A Swift utility to make updating table views/collection views trivially easy and reliable.

ArrayDiff An efficient Swift utility to compute the difference between two arrays. Get the removedIndexes and insertedIndexes and pass them directly a

Adlai Holler 100 Jun 5, 2022
MemoryCache - type-safe, thread-safe memory cache class in Swift

MemoryCache is a memory cache class in swift. The MemoryCache class incorporates LRU policies, which ensure that a cache doesn’t

Yusuke Morishita 74 Nov 24, 2022
Modern thread-safe and type-safe key-value observing for Swift and Objective-C

Now Archived and Forked PMKVObserver will not be maintained in this repository going forward. Please use, create issues on, and make PRs to the fork o

Postmates Inc. 708 Jun 29, 2022
A Protocol-Oriented NotificationCenter which is type safe, thread safe and with memory safety

A Protocol-Oriented NotificationCenter which is type safe, thread safe and with memory safety. Type Safe No more userInfo dictionary and Downcasting,

null 632 Dec 7, 2022
A Swift mixin for reusing views easily and in a type-safe way (UITableViewCells, UICollectionViewCells, custom UIViews, ViewControllers, Storyboards…)

Reusable A Swift mixin to use UITableViewCells, UICollectionViewCells and UIViewControllers in a type-safe way, without the need to manipulate their S

Olivier Halligon 2.9k Jan 3, 2023