SwiftBlocks 0.1.0

SwiftBlocks 0.1.0

Bill Panagiotopoulos 维护。



  • Vassilis Panagiotopoulos

Logo

一个用于使用可重用组件创建用户界面的 Swift 库。

Blocks 是一个 Swift 库,用于简化 iOS 应用程序中用户界面的创建。它允许开发者在同一个 UITableView 中使用 SwiftUI 组件和传统的 UITableView cells、headers 和 footers。这种方法方便了声明性 UI 的构建,同时保持了在必要时编写自定义代码的灵活性。受 React 启发,Blocks 鼓励开发可重用组件,促进更抽象和模块化的代码,这在大型和复杂项目中尤其有益。该库利用 UIKit 的 UITableView 作为组件的容器来渲染,并支持 MVVM 设计模式。

安装

您可以使用以下任一方式安装 Blocks...

CocoaPods

将以下行添加到您的 Podfile 中,并在终端中运行 pod install

pod 'Blocks', '~> 0.0.1'

Carthage

将以下行添加到您的 Carthage,并在终端中运行 carthage update

github "billp/Blocks" ~> 0.0.1

Swift 包管理器

转到 文件 > Swift 包 > 添加包依赖项,并添加以下 URL

https://github.com/billp/Blocks

表格视图渲染器

初始化

通过将其初始化器中的 table view 实例传递给一个新的 TableViewRenderer 实例来创建一个新TableViewRenderer实例。

// Create a lazy var for UITableView. You can also create the TableView in any way you want (Storyboard, Nib, etc.)
lazy var tableView: UITableView = {
    let tableView = UITableView()
    view.addSubview(tableView)
  
    tableView.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint.activate([
        view.topAnchor.constraint(equalTo: tableView.topAnchor),
        view.leftAnchor.constraint(equalTo: tableView.leftAnchor),
        view.bottomAnchor.constraint(equalTo: tableView.bottomAnchor),
        view.rightAnchor.constraint(equalTo: tableView.rightAnchor),
    ])

    return tableView
}()

// Create the Table View renderer and pass it a UITableView instance.
lazy var renderer = TableViewRenderer(tableView: tableView)

组件创建和注册

Blocks 通过定义符合 Component 协议的视图模型来启用创建灵活和可重用的 UI 组件。这些组件可以使用各种视图类型进行渲染,包括传统的 nib 文件(用于 UITableViewCellUITableViewHeaderFooterView),基于类的视图(用于无 nib 初始化),或 SwiftUI 类类型。这种方法允许在应用程序的 UI 中集成 UIKit 和 SwiftUI 元素,为 UI 开发提供了一整套灵活的工具。

传统的 UIKit 组件(基于 nib)

视图模型

定义一个符合 Component 协议的视图模型。

struct EmptyResultsComponent: Component {
    var title: String
}

单元格视图

实现一个符合 ComponentViewConfigurableUITableViewCell 子类,用于使用视图模型配置单元格。

class EmptyResultsViewCell: UITableViewCell, ComponentViewConfigurable {
    @IBOutlet weak var resultLabel: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        selectionStyle = .none
    }

    func configure(with viewModel: any Component) {
        guard let component = viewModel.as(EmptyResultsComponent.self) else { return }
        resultLabel.text = component.title
    }
}

Xib 文件

关联 xib 文件: EmptyResultsViewCell.xib

注册

将组件注册到渲染器来连接视图模型和相应的视图。

renderer.register(viewModelType: EmptyResultsComponent.self, nibName: String(describing: EmptyResultsViewCell.self))

此注册方法适用于使用 nib 文件的头和脚部。

SwiftUI 组件

视图模型

创建一个包含 SwiftUI 视图所用的属性的符合 Component 协议的视图模型。

class TodoComponent: ObservableObject, Component {
    var id: UUID = .init()
    @Published var title: String

    init(title: String) {
       self.title = title
    }
}

视图

构建一个符合 ComponentSwiftUIViewConfigurable 的 SwiftUI 视图,使用视图模型进行 UI 配置。

import SwiftUI

struct TodoView: View, ComponentSwiftUIViewConfigurable {
    @ObservedObject private var viewModel: TodoComponent

    init(viewModel: any Component) {
        self.viewModel = viewModel.as(TodoComponent.self)
    }

    var body: some View {
        // SwiftUI view layout using viewModel properties
    }
}

注册

SwiftUI 组件通过渲染器进行注册,以将视图模型与其 SwiftUI 视图连接起来,确保在基于 UIKit 的表格或集合视图中正确管理和渲染。

renderer.register(viewModelType: TodoComponent.self, viewType: TodoView.self)

本策略用于定义和注册组件,为构建应用的UI提供了一种模块化和可重用的方法,充分利用了UIKit和SwiftUI框架的优点。

使用方法

要使用renderer.updateSections更新UI,将TodoComponent与示例数据相结合,并使用EmptyResultsComponent处理空状态,可以遵循以下简化的步骤:

// Example method to update sections with todos and handle empty states
private func updateUI(withActiveTodos activeTodos: [TodoComponent], completedTodos: [TodoComponent]) {
    let activeSectionRows: [any Component] = activeTodos.isEmpty ? [EmptyResultsComponent(title: "No active todos.")] : activeTodos
    let completedSectionRows: [any Component] = completedTodos.isEmpty ? [EmptyResultsComponent(title: "No completed todos.")] : completedTodos
    
    let sections = [
        Section(id: "activeTodos", rows: activeSectionRows),
        Section(id: "completedTodos", rows: completedSectionRows)
    ]
    
    renderer.updateSections(sections, animation: .fade)
}

// Sample usage with active and completed todos
private func sampleUpdate() {
    let activeTodos = [
        TodoComponent(title: "Buy groceries"),
        TodoComponent(title: "Read a book")
    ]
    let completedTodos = [
        TodoComponent(title: "Workout"),
        TodoComponent(title: "Call mom")
    ]
    
    updateUI(withActiveTodos: activeTodos, completedTodos: completedTodos)
}