/TableKit

TableKit

Primary LanguageSwiftMIT LicenseMIT

Jenga - 基于Swift ResultBuilder优雅的构建UITableView

License  Swift  Platform  Swift Package Manager  Cocoapods

This framework allows you to build Table views using UIKit with syntax similar to SwiftUI. You can think about this as an improved UITableView.

Features

  • Use declarative chaining syntax to build lists Smooth coding experience Elegant and natural styling.
  • Rich Cell type support, support system setting styles and custom types.
  • Support @propertyWrapper, use state and binding to bind UI state
  • Support automatic calculation and row height
  • Support automatic registration of Cell
  • Continue to add more new features.

Screenshot

Simple

Setting

Installation

CocoaPods - Podfile

pod 'Jenga'

Select Xcode menu File > Swift Packages > Add Package Dependency and enter repository URL with GUI.

Repository: https://github.com/fanglinwei/Jenga

Add the following to the dependencies of your Package.swift:

.package(url: "https://github.com/fanglinwei/Jenga", from: "version")

Usage

First make sure to import the framework:

import Jenga

How to initialize:

JengaEnvironment.isEnabledLog = true  //日志
JengaEnvironment.setup(JengaProvider())

Then you just need short code to build UITableView

@TableBuilder
var tableBody: [Table] {
			rows...
}

Here are some usage examples. All devices are also available as simulators:

DSLAutoTable is recommended for fast builds:

import Jenga

class ViewController: UIViewController, DSLAutoTable {

    @TableBuilder
    var tableBody: [Table] {
        TableSection {
            
            NavigationRow("设置样式")
                .onTap(on: self) { (self) in
                    self.navigationController?.pushViewController(SettingViewController(), animated: true)
                }

            NavigationRow("自定义Cell")
                .onTap(on: self) { (self) in
                    self.navigationController?.pushViewController(CustomViewController(), animated: true)
                }
        }
    }
}

preview:

Stroke

Custom Cell:

@TableBuilder
    var tableBody: [Table] {
        
        TableSection {
            
            TableRow<BannerCell>("image1")
                .height(1184 / 2256 * (UIScreen.main.bounds.width - 32))
                .customize { [weak self] cell in
                    cell.delegate = self
                }
            
            SpacerRow(10)
            
            TableRow<BannerCell>()
                .height(1540 / 2078 * (UIScreen.main.bounds.width - 32))
                .data("image2")
                .customize { (cell, value) in
                    print(cell, value)
                }
        }
        .headerHeight(20)
    }

preview:

Stroke

State and Binding:

    @State var text = "objective-c"
    
    @State var detailText = "TableView"
    
    @State var isHiddenCat = false

    // DSL
    @TableBuilder
    var tableBody: [Table] {
        
        TableSection {
            NavigationRow($text)
                .detailText($detailText)
            
            ToggleRow("显示小猫", isOn: $isHiddenCat)
                .onTap(on: self) { (self, isOn) in
                    self.isHiddenCat = isOn
                }
            
        }
        .header("Toggle")
        .rowHeight(52)
        .headerHeight(UITableView.automaticDimension)
        
        TableSection(binding: $isHiddenCat) { isOn in
            NavigationRow("🐶")
            NavigationRow("🐶")
            NavigationRow("🐶")
  
            if isOn {
                NavigationRow("🐱")
                NavigationRow("🐱")
                NavigationRow("🐱")
            }
        }
        .header("Animal")
        .headerHeight(UITableView.automaticDimension)
    }

Modify State to update UI

text = "Swift"
detailText = "Jenga"
isShowCat = true

preview:

Stroke Stroke

Section Binding:

    @State var emojis: [String] = ["🐶", "🐱", "🐭", "🦁", "🐼"]
    
    // DSL
    @TableBuilder
    var tableBody: [Table] {
        
        TableSection(binding: $emojis) {
            TableRow<EmojiCell>()
                .data($0)
                .height(44)
        }
        .headerHeight(UITableView.automaticDimension)
        
        TableSection {
            TapActionRow("Random")
                .onTap(on: self) { (self) in
                    guard self.emojis.count > 3 else { return }
                    self.emojis[2] = randomEmojis[Int.random(in: 0 ... 4)]
                    self.emojis[3] = randomEmojis[Int.random(in: 0 ... 4)]
                }
            
            TapActionRow("+")
                .onTap(on: self) { (self) in
                    self.emojis.append(randomEmojis[Int.random(in: 0 ... 4)])
                }
            
            TapActionRow("-")
                .onTap(on: self) { (self) in
                    guard self.emojis.count > 0 else { return }
                    _ = self.emojis.popLast()
                }
        }
        .headerHeight(UITableView.automaticDimension)
    }

preview:

Stroke

It is also possible not to use TableSection, but I am still weighing the pros and cons of this API approach:

    @TableBuilder
    var tableBody: [Table] {
        
        TableHeader("我是头部")
        NavigationRow("设置样式")
        NavigationRow("自定义Cell")
        NavigationRow("自定义TableView")
        TableFooter("我是底部")
        
        TableHeader("第二组")
            .height(100)
        NavigationRow("cell")
    }

自定义DSLAutoTable创建的TableView

struct JengaProvider: Jenga.JengaProvider {
    
    func defaultTableView(with frame: CGRect) -> UITableView {
        let tableView: UITableView
        if #available(iOS 13.0, *) {
            tableView = UITableView(frame: frame, style: .insetGrouped)
        } else {
            tableView = UITableView(frame: frame, style: .grouped)
        }
        return tableView
    }
}

JengaEnvironment.setup(JengaProvider())

If you want to listen to UIScrollViewDelegate or create your own TableView, you can't use DSLAutoTable protocol

Just view CustomTableViewController in Demo

  1. TableDirector
    lazy var table = TableDirector(tableView, delegate: self)
  2. Describe TableBody using @TableBuilder
        @TableBuilder
        var tableBody: [Table]] {
            
            TableSection(binding: $array) {
                TableRow<EmojiCell>()
                    .data($0)
                    .height(44)
            }
            .headerHeight(UITableView.automaticDimension)
        }
  3. Update TableBody
    table.set(sections: tableBody)

Done, your table is ready.

For more examples, see the sample application.

Cell height calculating strategy:

Implementation ideas come fromFDTemplateLayoutCell

You can set height to UITableView.highAutomaticDimension to enable automatic calculation and cache row height

Just view AutoHeightViewController in Demo

// row
NavigationRow()
	.height(UITableView.highAutomaticDimension)

// section
TableSection {
  rows...
}
.rowHeight(UITableView.highAutomaticDimension)

SystemRow protocol provides chaining

Row 描述
text 标题
detailText 子标题(默认value1)
detailText(.subtitle) 子标题subtitle
detailText(.value1) 子标题value1
detailText(.value2) 子标题value2
detailText(.none) 子标题空样式
isOn 开关
height 行高
estimatedHeight 预估行高
selectionStyle 选中样式
onTap 点击事件
customize 自定义

Contributing

If you have the need for a specific feature that you want implemented or if you experienced a bug, please open an issue. If you extended the functionality of Jenga yourself and want others to use it too, please submit a pull request.

Thanks for inspiration

License

Jenga is under MIT license. See the LICENSE file for more info.