【问题标题】:Pull to refresh in a tableview rx datasource在 tableview rx 数据源中拉动以刷新
【发布时间】:2019-04-30 06:46:45
【问题描述】:

在我的移动应用程序中,我想通过拉取刷新请求来更新 tableView 数据源,但我不知道如何在 tableview 数据源顶部插入新项目。

我看到有一个 insertRows 的方法,例如:self.tableView?.insertRows(at: [indexPath], with: .top) 但是如何根据我的方法在此处添加 newItems?

我有一个名为 initializedTableView() 的函数,它使用 PublishSubject 可观察项初始化 tableView。

func initializeTableView() {

    viewModel
        .items
        .subscribe(onNext: { items in

            self.tableView?.delegate = nil
            self.tableView?.dataSource = nil

            Observable.just(items)
                .bind(to:(self.tableView?.rx.items(cellIdentifier: 
                 itemCell.Identifier, cellType: itemCell.self))!) { 
                 (index, element, cell) in

                    cell.itemModel = element

                }.disposed(by: self.disposeBag)
        })
        .disposed(by: disposeBag)
}

一旦用户请求拉动刷新,就会调用此函数:

func refreshTableView() {

    // get new items
    viewModel
        .newItems
        .subscribe(onNext: { newItems in

            //new
            let new = newItems.filter({ item in
                // items.new == true
            })

            //old
            var old = newItems.filter({ item -> Bool in
                // items.new == false
            })

            new.forEach({item in
                // how to update tableView.rx.datasource here???

            })

 }).disposed(by: disposeBag)
 }

【问题讨论】:

  • 你试过RxTableViewSectionedAnimatedDataSource吗?
  • 不,但我目前正在调查。关于如何开始的任何建议?我需要更改 initializedTableView() 的整个逻辑吗?

标签: ios swift tableview datasource rx-swift


【解决方案1】:
struct ViewModel {
    let items: BehaviorRelay<[Item]>

    init() {
        self.items = BehaviorRelay(value: [])
    }

    func fetchNewItems() {
        // This assumes you are properly distinguishing which items are new 
        // and `newItems` does not contain existing items
        let newItems: [Item] = /* However you get new items */

        // Get a copy of the current items
        var updatedItems = self.items.value

        // Insert new items at the beginning of currentItems
        updatedItems.insert(contentsOf: newItems, at: 0)

        // For simplicity this answer assumes you are using a single cell and are okay with a reload
        // rather than the insert animations.
        // This will reload your tableView since 'items' is bound to the tableView items
        //
        // Alternatively, you could use RxDataSources and use the `RxTableViewSectionedAnimatedDataSource`
        // This will require a section model that conforms to `AnimatableSectionModelType` and some
        // overall reworking of this example
        items.accept(updatedItems)
    }
}

final class CustomViewController: UIViewController {

    deinit {
        disposeBag = DisposeBag()
    }

    @IBOutlet weak var tableView: UITableView!

    private var disposeBag = DisposeBag()
    private let viewModel = ViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomTableCell.self, forCellReuseIdentifier: "ReuseID")
        tableView.refreshControl = UIRefreshControl()

        viewModel.items
            .bind(to: tableView.rx.items(cellIdentifier: "ReuseID", cellType: CustomTableCell.self)) { row, item, cell in
                // Configure cell with item
                cell.configure(with: item)
        }
        .disposed(by: disposeBag)

        tableView.refreshControl?.rx.controlEvent(.valueChanged)
            .subscribe(onNext: { [weak self] in
                self?.viewModel.fetchNewItems()
            })
            .disposed(by: disposeBag)
    }
}

使用BehaviorRelay 和绑定的替代答案。这样,您只更新items 中继,它会自动更新tableView。它还提供了一种更“Rx”的方式来处理拉取刷新。

正如代码 cmets 中所述,这假定您正在确定哪些项目是新的并且newItems 不包含任何现有项目。无论哪种方式,这都应该提供一个起点。

【讨论】:

    【解决方案2】:
    struct ViewModel {
        let items: Observable<[Item]>
    
        init(trigger: Observable<Void>, newItems: @escaping () -> Observable<[Item]>) {
            items = trigger
                .flatMapLatest(newItems)
                .scan([], accumulator: { $1 + $0 })
        }
    }
    

    上面不处理错误,也不处理重置,但scan 会将新项目放在列表的顶部。

    但情况感觉不太对劲。通常,API 调用会返回所有的项目,它怎么可能知道哪些项目是“新的”?

    【讨论】:

      【解决方案3】:

      由于tableView.insertRows 有问题,我对我的应用做了类似的事情。

      代码如下:

      func loadMoreComments() {
          // call to backend to get more comments
          getMoreComments { (newComments) in
              // combine the new data and your existing data source array
              self.comments = newComments + self.comments
              self.tableView.reloadData()
              self.tableView.layoutIfNeeded()
              // calculate the total height of the newly added cells
              var addedHeight: CGFloat = 0
              for i in 0...result.count {
                  let indexRow = i
                  let tempIndexPath = IndexPath(row: Int(indexRow), section: 0)
                  addedHeight = addedHeight + self.tableView.rectForRow(at: tempIndexPath).height
              }
              // adjust the content offset by how much height was added to the start so that it looks the same to the user
              self.tableView.contentOffset.y = self.tableView.contentOffset.y + addedHeight
          }
      }
      

      因此,通过计算要添加到开头的新单元格的高度,然后将此计算出的高度添加到 tableView.contentOffset.y,我能够无缝地将单元格添加到 tableView 的顶部,而无需重新修改我的 tableView。这可能看起来像一个生涩的解决方法,但如果您正确计算高度,tableView.contentOffset 的变化并不明显。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多