SkillAgentSearch skills...

Jenga

Write table views in crazy speed,

Install / Use

/learn @fanglinwei/Jenga

README

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

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

Screenshot

<img src="Resources/simple.png" alt="Simple" width="80%" /> <div align="center"> <img src="Resources/setting.png" alt="Setting" width="40%" /> </div>

Installation

CocoaPods - Podfile

pod 'Jenga'

Swift Package Manager for Apple platforms

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

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

Swift Package Manager

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

.package(url: "https://github.com/fanglinwei/Jenga.git", 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:

<div align="center"> <img src="Resources/quick.png" alt="Stroke" width="40%" /> </div>

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:

<div align="center"> <img src="Resources/custom.png" alt="Stroke" width="40%" /> </div>

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(.automaticDimension)
        
        TableSection(binding: $isHiddenCat) { isOn in
            NavigationRow("🐶")
            NavigationRow("🐶")
            NavigationRow("🐶")
  
            if isOn {
                NavigationRow("🐱")
                NavigationRow("🐱")
                NavigationRow("🐱")
            }
        }
        .header("Animal")
        .headerHeight(.automaticDimension)
    }

Modify State to update UI

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

preview:

<div align="center"> <img src="Resources/binding_1.png" alt="Stroke" width="40%" /> <img src="Resources/binding_2.png" alt="Stroke" width="40%" /> </div>

Section Binding:

    @State var emojis: [String] = ["🐶", "🐱", "🐭", "🦁", "🐼"]
    
    // DSL
    @TableBuilder
    var tableBody: [Table] {
        
        TableSection(binding: $emojis) {
            TableRow<EmojiCell>()
                .data($0)
                .height(44)
        }
        .headerHeight(.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(.automaticDimension)
    }

preview:

<div align="center"> <img src="Resources/section_binding.png" alt="Stroke" width="40%" /> </div>

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

Custom DSLAutoTable to create 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(.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 RowHeight.highAutomaticDimension to enable automatic calculation and cache row height

Just view AutoHeightViewController in Demo

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

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

SystemRow protocol provides chaining

| modify | description | | :---------------------- | ------------------ | | text | | | detailText | UITableViewCell.CellStyle.value1 |
| detailText(.subtitle) | UITableViewCell.CellStyle.subtitle | | detailText(.value1) | UITableViewCell.CellStyle.value1 | | detailText(.value2) | UITableViewCell.CellStyle.value2 | | detailText(.none) | no detailText | | isOn | switch | | height | constant(CGFloat), automaticDimension, highAutomaticDimension | | estimatedHeight | constant(CGFloat), automaticDimension, highAutomaticDimension | | selectionStyle | | | onTap | cell didSelected | | customize | modify cell | | textAlignment | TapActionRow text alignment | | accessoryType | NavigationRow accessoryType |

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

View on GitHub
GitHub Stars67
CategoryDevelopment
Updated5mo ago
Forks5

Languages

Swift

Security Score

97/100

Audited on Oct 14, 2025

No findings