具有大标题导航的UITableView UIRefreshControl不会停留在顶部-它会移动

问题描述 投票:2回答:2

我已经使用带有大标题的UITableView.refreshControl。我正在尝试模仿本机Mail应用程序与pull刷新一起工作的方式。对我来说,刷新控件会移动,并且不会像Mail应用程序那样停留在屏幕顶部。这是一个游乐场:

//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
    public init() {
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private lazy var refresh: UIRefreshControl = {
        let refreshControl = UIRefreshControl()
        refreshControl.backgroundColor = .clear
        refreshControl.tintColor = .black
        refreshControl.addTarget(self, action: #selector(refreshIt), for: .valueChanged)
        return refreshControl
    }()
    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.refreshControl = refresh
        tableView.delegate = self
        tableView.dataSource = self
        return tableView
    }()
    private var data: [Int] = [1,2,3,4,5]

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        // Add tableview
        addTableView()

        navigationController?.navigationBar.prefersLargeTitles = true
        navigationItem.title = "View Controller"
    }
    private func addTableView() {
        view.addSubview(tableView)
        NSLayoutConstraint.activate([
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
    @objc func refreshIt(_ sender: Any) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.refresh.endRefreshing()
            self.data = [6,7,8,9,10]
            self.tableView.reloadData()
        }
    }
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        1
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.data.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = "Row \(data[indexPath.row])"
        return cell
    }
}
let vc = ViewController()

let nav = UINavigationController(rootViewController: vc)

// Present the view controller in the Live View window
PlaygroundPage.current.liveView = nav

我如何使微调器固定在顶部,并使它的行为类似于Mail应用程序?另外,导航标题的作用很奇怪,它向后移动的速度比表数据慢,并导致发生一些重叠...感谢您提供任何帮助!

2020年6月7日编辑

我切换为使用可修复重叠动画的可扩散数据源,但是,当触觉反馈发生并导致微调器从顶部向上向下倾斜时,仍然存在这种奇怪的故障,因此我的原始问题仍然存在-我们如何使微调框像邮件应用程序一样固定在顶部。感谢您关注这个独特的问题:)!!!

ios swift xcode uitableview uirefreshcontrol
2个回答
0
投票

我真的不知道如何回答第一个问题,但是既然您问了两个问题,我将继续努力,同时尝试第二个问题。

行到达顶部的速度过快的问题是由于使用了tableView.reloadData()。这总是在没有动画的情况下完成的,因此发生的事情是可以预期的。

您可以做的是在表视图上使用其他方法,例如像这样插入或删除行:

@objc func refreshIt(_ sender: Any) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.refresh.endRefreshing()


            let newData = [6,7,8,9,10]
            let newIndexPaths = newData.enumerated().map{IndexPath(row: self.data.count+$0.offset, section: 0)}

            self.data.append(contentsOf: newData)
            self.tableView.insertRows(at: newIndexPaths, with: .automatic)
        }
    }

以这种方式,将在行中添加动画,并且向下滚动动画不会被任何reloadData()中断。

如果您没有仅要添加(或删除)的行,并且可能需要对两者进行某种组合,则可以使用performBatchUpdates方法在同一动画块中进行添加和删除。 (iOS 11 +)

当然,只有在您能够计算要添加和删除的项目的索引时,这才可行。请记住,添加和删除的行数应始终由数据结构中的实际更改反映出来,否则表视图会引发令人讨厌的异常。

无论如何,总应该是可行的,但是如果您要开箱即用,则可以使用iOS 13 diffable data source,或者,如果要在iOS 13之前使用,请使用instagram的IGListKit

两者几乎都使用相同的概念来区分输入数组并自动检测删除和插入。因此,更改数组将自动反映在tableView行中的动画更改中。当然,它们都是实现tableView的非常不同的方式,因此您真的不能只更改代码的一部分来使其工作。

希望这会有所帮助。


0
投票

原因是在使用中reloadData,它会同时删除并重建所有内容,从而破坏了最终刷新的动画。

可能的解决方案是给动画结束时间,然后再执行重新加载数据。

使用Xcode 11.4 /游乐场测试

demo

修改后的代码:

@objc func refreshIt(_ sender: Any) {
    // simulate loading
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        self.refresh.endRefreshing() // << animating

        // simulate data update
        self.data = [6,7,8,9,10]

        // forcefully synchronous, so delay a bit
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.tableView.reloadData()
        }
    }
}

替代方法,如果您知道修改后的数据结构,将使用beginUpdate/endUpdate

demo2

@objc func refreshIt(_ sender: Any) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        self.refresh.endRefreshing()
        self.data = [6,7,8,9,10]

        // more fluent, but requires knowledge of what is modified,
        // in this demo it is simple - first section
        self.tableView.beginUpdates()
        self.tableView.reloadSections(IndexSet([0]), with: .automatic)
        self.tableView.endUpdates()
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.