【问题标题】:UITableView got displayed before data from API using CodableUITableView 在使用 Codable 的 API 数据之前显示
【发布时间】:2021-09-14 15:43:05
【问题描述】:

当我运行应用程序时,我可以看到一个空白表格,如下面的屏幕截图,加载了特定的毫秒数,然后用实际数据加载表格。由于 items 数组开头有 0 个元素,numberOfRowsInSection 返回 0 和空白表视图正在加载。是这样吗?请帮帮我

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

我将上面的代码更改为下面的代码,但存在同样的问题,在调试模式下我发现 print("Item array is empty") 执行了两次,然后空白表视图显示了几秒钟, 之后实际的 API 调用发生并且数据正确显示在 tableview 中

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if items.isEmpty{
        print("Item array is empty")
        return 0
    } else {
        return self.items.count
    }
}
import UIKit
class MainVC: UIViewController,UITableViewDelegate,UITableViewDataSource {

    @IBOutlet weak var bookslideShow: UIView!
    @IBOutlet weak var bookTableView: UITableView!
    var items : [Items] = []

    override func viewDidLoad() {

        super.viewDidLoad()
        bookTableView.dataSource = self
        bookTableView.delegate = self
        self.view.backgroundColor = UIColor.lightGray
        bookTableView.rowHeight = 150
        // self.view.backgroundColor = UIColor(patternImage: UIImage(named: "background.jpeg")!)

        self.fetchBooks { data in
            self.items.self = data
            DispatchQueue.main.async {
                self.bookTableView.reloadData()
            }
        }
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if items.isEmpty{

            print("Item array is empty")
            return 0

        } else {
            return self.items.count
            //bookTableView.reloadData()
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "BookCell",for:indexPath) as! BookCell
        //cell.backgroundColor = UIColor(red: 180, green: 254, blue: 232, alpha: 1.00)
        let info = items[indexPath.row].volumeInfo
        cell.bookTitle.text = info.title
        cell.bookCategory.text = info.categories?.joined(separator: ",")
        cell.bookAuthor.text = info.authors?.joined(separator: ", ")
        let imageString = (info.imageLinks?.thumbnail)!


        if let data = try? Data(contentsOf: imageString) {
            if let image = UIImage(data: data) {
                DispatchQueue.main.async {
                    cell.bookImage.image = image
                }
            }
        }
        return cell
    }

    func fetchBooks(comp : @escaping ([Items])->()){

        let urlString = "https://www.googleapis.com/books/v1/volumes?q=quilting"
        let url = URL(string: urlString)

        guard url != nil else {
            return
        }
        let session = URLSession.shared

        let dataTask = session.dataTask(with: url!) { [self] (data, response, error) in
            //check for errors
            if error == nil && data != nil{
                //parse json

                do {
                    let result = try JSONDecoder().decode(Book.self, from: data!)
                    comp(result.items)
                }
                catch {
                    print("Error in json parcing\(error)")
                }
            }
        }
        //make api call
        dataTask.resume()
    }
}

【问题讨论】:

    标签: ios swift api tableview codable


    【解决方案1】:

    委托方法可以被多次调用。如果您想最初删除那些空单元格。你可以在 viewDidLoad 中添加这个:

    bookTableView.tableFooterView = UIView()
    

    【讨论】:

      【解决方案2】:

      您正在穿越网络获取数据。这可能需要很长时间,尤其是在连接速度很慢的情况下。只要用户了解正在发生的事情,如果您在网络上等待,空的 tableview 不一定是坏事。几个解决方案,

      1. 在应用程序启动的早期获取数据并在本地存储。这种方法的问题是用户可能永远不需要下载的资源。例如,如果 Instantgram 这样做了,那将是用户不需要的巨大下载。如果您知道该资源将被完全使用,请尽早获取它,或者至少获取您知道将被使用的一小部分资源。

      2) 甚至在 segue 之前就开始获取它。在您的代码中,您需要它用于表格视图,但您一直在等待视图加载。这在生命周期中已经很晚了。

      3) 如果您必须让用户等待资源,请让他们知道您正在加载。表格视图有一个刷新控件,您可以在网络上等待时调用该控件,或者使用进度指示器或微调器。您甚至可以隐藏整个视图并呈现一个视图,以便用户知道发生了什么。

      tableview 在自动加载时调用数据源,当你在代码中说 reloadData() 时调用它,这就是你得到两个调用的原因。

      因此,要回答您的问题,这可以通过多种方式完成,您可以创建一个协议或对象实例的本地副本,即:在您的 presentingViewController 中的 MainVC 然后将您的获取代码移动到那里并在本地副本上设置项目当提取回来时。并且只需向 items 变量添加一个 didset 以在设置变量时重新加载 tableview。或者,您理论上至少可以在传递块中的 MainVC 项的 segue 中执行 fetch 块。

      例如

       override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
          let vc = segue.destination as MainVC
           
          self.fetchBooks { data in
              vc.items.self = data // not sure what the extra self is?
              DispatchQueue.main.async {
                  vc.bookTableView.reloadData()
              }
          }
          
       }
      

      由于闭包捕获了一个强指针,因此您可以这样做。

      【讨论】:

      • 感谢您提供详细信息。能否请您详细说明第二种方式,即在海格之前提前取货
      【解决方案3】:

      通常我会做如下代码所示的数据任务,请看代码中的cmets。

      // show a spinner to users when data is loading
      self.showSpinner()
      DispatchQueue.global().async { [weak self] in
          
          // Put your heavy lifting task here,
          // get data from some completion handler or whatever
          loadData()
      
          // After data is fetched OK, push back to main queue for UI update
          DispatchQueue.main.async {
              self?.tableView.reloadData()
              // remove spinner when data loading is complete
              self?.removeSpinner()
          }
      }
      

      【讨论】:

      • 注意:qos: .background 无法保证任务完成或什至完全运行,具体取决于 CPU 使用率和内存压力。
      • 注意,我会检查哪个 qos 限定符适合并更新我的答案。
      • 更新,使用default qos 代替background qos。
      猜你喜欢
      • 1970-01-01
      • 2021-09-27
      • 1970-01-01
      • 2015-06-26
      • 2012-08-10
      • 2021-05-06
      • 1970-01-01
      • 1970-01-01
      • 2011-01-25
      相关资源
      最近更新 更多