RxSwift:删除 UITableView 中的行,而无需重新加载整个表

RxSwift: Remove a row in UITableView without reloading whole table

提问人:Mando 提问时间:7/1/2023 更新时间:7/1/2023 访问量:70

问:

在不使用 RxSwift 时,我可以通过执行以下操作来实现这一点。它运行良好,在被删除的行上播放所需的动画,并且不会重新加载整个表:

let item = self.dataSource[(indexPath as NSIndexPath).row]
self.dataSource.remove(at: (indexPath as NSIndexPath).row)
tableView.deleteRows(at: [indexPath], with: .right)

使用 RxSwift,我通过这样做将数据绑定到 tableView,当我接受新数组时,它会重新加载整个表并且不会播放删除动画:

// ViewModel:
let items: BehaviorRelay<[Item]> = BehaviorRelay(value: [])

func removeItem(_ item: Item) {
    var loadedItems = items.value
    if let removeIndex = loadedItems.firstIndex(of: item) {
        loadedItems.remove(at: removeIndex)
    }
    items.accept(loadedItems)
}

//ViewController:
viewModel.items
    .bind(to: tableView.rx.items) { [weak self] (table, index, item) in
        // Creating cell for the `item`....
        return cell   
    }
    .disposed(by: self.disposeBag)

如何更新源以删除单个元素并确保删除动画正在桌面视图上播放?viewModel.item

iOS 版Swift UITableView RX-Cocoa

评论


答:

1赞 Daniel T. 7/1/2023 #1

有一个名为 RxDataSources 的库实现了这个东西。或者,如果您的需求很简单,您可以自己做。只需实现一种必需的方法。

下面是一个示例:

class Example: NSObject, RxTableViewDataSourceType, UITableViewDataSource {
    private var items = [Item]()

    func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<[Item]>) {
        switch observedEvent {
        case let .next(items):
            // figure out which items have been added and which have been removed.
            // animate the table view as usual.
            // then:
            self.items = items
        default:
            break
        }
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // build a cell as usual.
    }
}

您可以通过简单地绑定它来使用它,如下所示:

func example(source: Observable<[Item]>, tableView: UITableView, disposeBag: DisposeBag) {
    source
        .bind(to: tableView.rx.items(dataSource: Example()))
        .disposed(by: disposeBag)
}

您可以通过在可观察对象上发出一个新数组来添加/删除单元格。source


下面是使用 RxDataSources 的完整示例(如果选择使用它):

typealias ItemSection = AnimatableSectionModel<String, Item>

func example(source: Observable<[ItemSection]>, tableView: UITableView, disposeBag: DisposeBag) {
    let dataSource = RxTableViewSectionedAnimatedDataSource<ItemSection>(
        animationConfiguration: AnimationConfiguration(deleteAnimation: .right), // since you specified right delete in your question
        configureCell: { dataSource, tableView, indexPath, item in
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyTableViewCell
            cell.configure(with: item)
            return cell
        }
    )

    source
        .bind(to: tableView.rx.items(dataSource: dataSource))
        .disposed(by: disposeBag)
}

struct Item: IdentifiableType, Equatable {
    let identity: Int
    let name: String
}

final class MyTableViewCell: UITableViewCell {
    func configure(with item: Item) {
        self.textLabel?.text = item.name
    }
}

注意:在这两种情况下,您都不需要存储数据源的属性,RxCocoa 库将为您处理。

评论

0赞 Mando 7/8/2023
听起来很复杂,我宁愿回退到 RxSwiftless 表渲染。感谢您的样品!
0赞 Mando 7/8/2023
我又试了一次,得到以下错误:Instance method 'items(dataSource:)' requires that 'TableViewSectionedDataSource<ItemSection>' (aka 'TableViewSectionedDataSource<AnimatableSectionModel<String, Item>>') conform to 'RxTableViewDataSourceType'
0赞 Daniel T. 7/8/2023
然后你没有使用我的示例代码。如果您将我的答案中的最后一个代码块复制粘贴到具有正确导入的文件中,它将编译