Skip to content

fanglinwei/Jenga

Repository files navigation

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.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:

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(.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:

Stroke Stroke

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:

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

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

License

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