Giter Site home page Giter Site logo

tablekit's Introduction

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.

tablekit's People

Contributors

alexs555 avatar astrokin avatar bellebethcooper avatar dmertsalov avatar jesster2k10 avatar maxsokolov avatar maxsokolovavito avatar motylevm avatar mrojas avatar mrtokii avatar petropavel13 avatar sc0rch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tablekit's Issues

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)

Update action syntax

Hello, can you please add this method. I think this small update will really improve action chaining syntax.

You can add following method in TableRow class

public func action<T>(type: TableRowActionType, handler: (data: TableRowActionData<ItemType, CellType>) -> T) -> Self {
    actions[type.key] = TableRowAction(type, handler: handler)
    return self
 }

This will lead to much nicer syntax:

let row = TableRow<String, StringTableViewCell>(item: "some")
    .action(.click) { (data) in

    }
    .action(.shouldHighlight) { (data) -> Bool in
        return false
    }

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, утечка остается просто текст утечки немного меняется

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

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

Swift 3

Hello, do you have swift 3 version?

Crash on reload of empty table if tableView contains tableHeaderView | iOS 10.3.1

On iOS 12 no crashes.

Code to reproduce the issue:

class ViewController: UIViewController {
    
    private let tableView = UITableView()
    
    lazy var tableDirector = TableDirector(tableView: tableView)
    
    override func loadView() {
        view = tableView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.tableHeaderView = UIView()
        
        tableDirector.clear()
        tableDirector.reload()
    }

}

Prepend TableDirector mutable functions with @discardableResult

Swift 3 version produces warnings about unused return values. For example,
you need to change next function

    open func append(section: TableSection) -> Self {

        _ = append(sections: [section])
        return self
    }

to

 @discardableResult  open func append(section: TableSection) -> Self {

        _ = append(sections: [section])
        return self
    }

Нигде не вызывается invalidate() у PrototypeHeightStrategy

Нигде не вызывается invalidate() у PrototypeHeightStrategy. Это иногда приводит к непредвиденному поведению и странному отображению ячеек при перезагрузке данных. Высоты начинают выдаваться не тем ячейкам.
Вероятно invalidate() должен вызываться в clear() у TableDirector.

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.

Dynamic cell height based on configured item

Issue: some cell might have different static heights for each state of item. As a result now, I have to create a two copies of cell with different heights, because cell class only has ability to return it height from the static variable. I know I can use AutoLayout, but it actually much slower and I need to improve performance here.
I propose to deprecate this static variables:

    static var defaultHeight: CGFloat? { get }
    static var estimatedHeight: CGFloat? { get }

And replace it with instance functions:

    func estimatedHeight(with _: T) -> CGFloat?
    func heigth(with _: T) -> CGFloat?

With this approach it is still possible to return static value, but in the same time much more flexible for user.

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.

ConfigurableCell T triggers SwiftLint warning

Hey, @maxsokolov!

We have a minor issue with ConfigurableCell. Every time we implement ConfigurableCell and provide a typealias for T it triggers a SwiftLint warning based on type_name rule (which is enabled by default in SwiftLint):

../CustomTableCell.swift:13:15: warning: Type Name Violation: Type name should be between 3 and 60 characters long: 'T' (type_name)

associatedtype T

As a workaround we disable this warning every time we implement ConfigurableCell protocol. It would be nice to rename T into something more meaningful like TableDisplayData or something similar to prevent this warning form being triggered by SwiftLint.

[Question] [macOS] Any recommendations?

@maxsokolov you did a really great job. i've successfully used TableKit in more then 5 last projects. But currently i've an iOS project needed to be ported to macOS version. What would you recommend, some analogue of TableKit for macOS?

How to activate ability to delete a row?

I am likely TableKit so far, but am used to more manual styles for setting up row deletion (and Parse). I find no examples of this within TableKit, only refs to .CanDelete but am not sure how to structure the code.

I am more interested in integrating Dwiff into TableKit but thought I should master this side first.

Support for moving rows?

I see that the methods in the delegate to move rows are not implemented, and there is no associated action, thus getting:

NSInvalidArgumentException', reason: '-[TableKit.TableDirector tableView:moveRowAtIndexPath:toIndexPath:]: unrecognized selector sent to instance 0x6040000d7ae0

Am I missing something, or is not yet implemented? I can do it if needed.

Cheers

Update sections with animation

Max hello! Could you say, how i can implement reloading section with animation?
Usually i do it like this:

let range = NSMakeRange(0, (self?.tableView.numberOfSections)!)
let sections = NSIndexSet(indexesIn: range)
self?.tableView.reloadSections(sections as IndexSet, with: .automatic)

I tried a lot of different ways to do it with tablekit, but i have runtime errors that either sections or rows are not correctly calculated by the table itself before and after the update.

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

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
    }
}

[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?

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

Remove/delete a section

Hello, there
I think i've faced some strange(at least as for me) behavior managing sections.
"Insert", "append", "replace" work perfectly, but "remove" and "delete" are different.
It seems like user has to call "tableDirector.reload()" explicitly after calling "remove" or "delete" otherwise no changes are applied to the table.
I find it uncleare, that only these to functions require explicit reload, and unfortunatly no desctiption is attached to functions for clarification.

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?

How to reload a concrete row or cell?

How can I reload a concrete row or cell? If I try to enumerate sections and rows inside them, then I'm unable to perform type casting ("Row" class has no useful data, but casting to "TableRow" is impossible due to its generic parameter). Also from the "Row" I can't access its data to read or modify it. Replacing the row with the new TableRow and reconfiguring it is very inconvenient and inappropriate for me.

Add untyped TableRowAction

I want to have ability to add untyped actions to any Row object.
Problem: I can't add the same action to differently typed TableRow objects because it required type matching. Even more, it not possible to split code and move code that built views (creating rows with particular cell) away from code that react on user input (some general actions).
Intent: Some actions aren't required to know what kind of object is passed to cell or what kind of cell was targeted. Allow to add general action to objects that covered by protocol Row.

How to get rid of AutoLayout warning when using TableKit

Hello.
How should I implement constraints to get rid of these warnings. I see it comes when table view requests cells for index path:
screen shot 2019-01-21 at 5 05 56 pm
I setup cells like subview.top == cell.contentView.top, subview.bottom == cell.contentView.bottom.

[QUESTION] Is there any way to customize rows appending/deleting

Hello!

In our application we animate rows deletion/addition/modification with UITableView's deleteRows, insertRows and reloadRows inside performBatchUpdates. I haven't found any of these functions are called in TableDirector, does it mean it always use reloadData to update table?
Is there any way to customize this behaviour, for example, to call insertRows if I'm going to add some rows to exiting section?

//Thanks!

Pagination?

How to work with the table Director, if I need to load cells when scrolling UITableView. Maybe you have some sample about this situation

Plain table with no header or footer still shows the header/footer

If I initialize a new section, using either of the constructors that take a header/footer string or view and pass nil for both, when I run, the table still shows empty header/footer views, instead of not showing them. For now, I end up needing to set the footer height to CGFloat.leastNormalMagnitude to make them go away. Can this be done in the framework for us?

Поддержка нескольких действий одного типа

@maxsokolov Привет! Спасибо за либу)

Расскажи, это так задумано, что если добавлять два действия одного типа, то выполняется только последний?

Мы просто написали экстеншн к TableRowAction с дефолтными методами по кастомизации и вызываем это таким образом:
TableRow<TaskClientInfoCell>(item: task, actions: [.hideSeparatorAction(), .disableSelectionAction()])
но, как я уже описал выше, выполняется только последний экшн.

Я сейчас в форке сделаю поддержку нескольких экшенов одного типа, нужно потом PR делать или вам такое не надо?

Crash when doing clear()

I get a crash when i do:

tableDirector.clear()
tableDirector.refresh() 

It tries to do an invoke of "didEndDisplaying" but the sections/data is gone.

How do I clear the data in the correct way?

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?

ConfigureCell не сохраняется значение

Привет! Используя демо проект заменил UILabel на UITextField в NibTableViewCell и в NibTableViewCell.swift сделал так:

func configure(with number: Int) {
    
 }

Сделал 20 ячеек. В первой ячейке ввожу текст в UITextField и скролю - в новых ячейках появляется такой же текст.
Да, понятно, что используется reusable cell, но подскажите, пожалуйста, best way чтобы "сохранялись ячейки" используя TableKit?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.