【问题标题】:Load CollectionView cells only when all data is loaded仅在加载所有数据时加载 CollectionView 单元格
【发布时间】:2019-03-05 18:20:15
【问题描述】:

我正在使用在 ViewDidLoad() 方法中调用的 loadData() 填充我的 CollectionView。在这里,我将 Firebase 实时数据库中的所有数据解析为一个数组(帖子)。然后,在 cellForItemAt 方法中,我使用 indexPath.item 根据帖子数组中的信息相应地设置所有图像、标签和文本视图。很基本的东西。

但是,我的数据库中有两个表:posts 和 users。在帖子中,我只收集有关帖子的信息和作者的用户 ID。然后我想从用户那里获取数据,因为个人资料图片和用户名会随着时间而改变,所以我不想让它粘在数据库的帖子表中。

我之前遇到的问题:我从 loadData() 中的帖子中加载了数据,然后会根据帖子数组中保存的 userID 在 cellForItemAt 方法中获取用户信息。这导致我的应用程序不稳定:滚动到新单元格启动了 cellForItemAt 方法,导致它请求数据,然后更新它。因此,由于必须下载信息,因此会有延迟。绝对确定这是原因,因为我现在将其设置为默认图像(无个人资料图片图像)和默认用户名(“用户名”),使其再次变得非常流畅。

然后我继续获取 userData 并将其解析到另一个数组 (userInfo):

struct userData {
    var userFirstName: String
    var userLastName: String
    var userProfilePicURL: String
}

var userInfo = [String : userData]()

我可以将其用作 userInfo[posts.userID],这正是我所寻找的。我现在遇到的问题是 userInfo 没有及时填充,当我将数组转储到 cellForItemAt 时返回 nil:

dump(userInfo[post.userID])

所以这在加载应用程序时返回 nil,但是当我滚动并因此再次初始化 cellForItemAt 时,它确实返回值。所以我有根据的猜测是数据没有及时获取。我现在正在寻找一种仅在加载帖子数组和用户数组时调用 cellForItemAt 的方法。

我如何向用户数组添加值,在loadData函数中,其中dict[“userID”]是通过观察数据库中的帖子获得的:

Ref.child("users").child(dict["userID"] as! String).observeSingleEvent(of: .value) { (snapshot) in

    let userValues = snapshot.value as! [String: Any]
    userInfo[dict["userID"] as! String] = userData(userFirstName: userValues["userFirstName"] as! String, userLastName: userValues["userLastName"] as! String, userProfilePicURL: userValues["userProfilePicURL"] as! String)

}

我想确保在显示单元格之前将信息添加到数组中,以便他们可以相应地更改个人资料图片和用户名。我想在 cellForItemAt 方法中执行此操作。我考虑过使用计时器,将我的 CollectionView 隐藏几秒钟,但这完全取决于连接速度等,所以我认为应该有更合适的解决方案。

欢迎任何有用的想法!

【问题讨论】:

  • 所以你只想在两个数组都填满数据时才加载集合视图,对吧?
  • 是的,没错。

标签: ios arrays swift firebase-realtime-database uicollectionview


【解决方案1】:

您可以从不需要加入 collectionview 的委托和数据源的情节提要中实现这一点。在控制器类中,当您获取数据后,只需设置 collectionview.delegate = self 和 datasource 并重新加载该集合视图。

【讨论】:

  • 不使用任何故事板,用代码编写所有内容。但是,在收集所有信息后,我可以设置我的委托和数据源,重新加载它会起作用吗?这是最佳做法吗?
  • 是的,我想是的,但是委托和数据源以及重新加载应该保留在另一种方法中。
  • 我同意你的看法。在代码中,我曾经在侧 viewDidLoad() 方法中添加委托和数据源。每当我想等到我的数组填满来自远程服务器的数据时,我会在完成填充数据后添加委托和数据源,所以一切都很好......并且 collectionView 可以看到数组中的数据。
【解决方案2】:

您可以使用 Apple 在IOS 10 中引入的Prefetching 机制。他们已通过以下链接中的示例对其进行了解释。

https://developer.apple.com/documentation/uikit/uicollectionviewdatasourceprefetching/prefetching_collection_view_data

希望它能解决你的问题。

【讨论】:

  • 一定会调查的。由于大多数 iOS 用户都在 iOS10+ 上,因此可能不会成为问题,对吧?我会看看我能从你提供的 URL 中得到什么,谢谢!
  • 是否应该将当前在 cellForItemAt 中填充数据的所有代码替换为由 CustomDataSource 创建的新 cellForItemAt 协议?还有其他协议(例如 sizeForItemAt)?
  • @PennyWise 否,但您必须更改现有函数中的逻辑/代码,例如 cellForItemAtsizeFormItemAt。但在此之前,您必须添加prefetchItemsAt 函数(UICollectionViewDataSourcePrefetching 协议)
【解决方案3】:

在这种情况下我会怎么做,

  1. 不要将集合视图的委托设置为您的控制器。
  2. 执行 Firebase 请求 1 以将数据加载到您的阵列 1。 在第一个请求的完成内,调用另一个执行 request2 的函数来获取数据并将其加载到数组 2 中。
  3. 在第二个请求的完成处理程序中,设置委托并重新加载数据(在主线程中)

如果您不想要嵌套调用。您可以并行触发两个请求并等待调用完成 然后设置委托并重新加载数据

请参阅此Helpful answer 了解如何操作的详细信息。

希望对你有帮助

【讨论】:

    【解决方案4】:

    你问了很多事情。是的,在显示视图时可能无法加载数据。幸运的是,UICollectionView 的设计考虑到了这一点。我建议观看“让我们构建那个应用程序”YouTube 频道,了解有关使用 UICollectionView 的好技巧。

    我们对远程服务器进行了大量动态搜索,因此内容会不断更新,甚至可能会在我们加载时更新。这是我们如何处理它。

    cellForItemAt 不是进行服务器调用的时候。加载数据后使用它。一般来说,不要将服务器加载与数据显示混为一谈。编写 UICollectionView 的目的是在内部项目列表上进行操作。这也将使您的代码更具可读性。

    添加您的慢速服务器函数,以便它们快速更新数据并在您完成后调用 reloadData()。我将在下面提供代码。

    在我们的 UICollectionViewController 中,我们保留了一个包含当前内容的数组,就像这样。我们从这些变量开始:

    var tasks = [URLSessionDataTask]()
    var _entries = [DirectoryEntry]()
    var entries: [DirectoryEntry] {
        get {
            return self._entries
        }
        set(newEntries) {
            DispatchQueue.main.async {
                self._entries = newEntries
            }
        }
    }
    

    每次从服务器获取数据时,我们都会执行以下操作:

    func loadDataFromServer() {
    
        // Cancel all existing server requests
        for task in tasks {
            task.cancel()
        }
        tasks = [URLSessionDataTask]()
    
        // Setup your server session and connection
        // { Your Code Here }
    
        let task = URLSession.shared.dataTask(with: subscriptionsURL!.url!, completionHandler: { data, response, error in
            if let error = error {
                // Process errors
                return
            }
    
            guard let httpresponse = response as? HTTPURLResponse,
                (200...299).contains(httpresponse.statusCode) else {
                    //print("Problem in response code");
                    // We need to deal with this.
                    return
            }
            // Collect your data and response from the server here populate apiResonse.items
            // {Your Code Here}
            var entries = [DirectoryEntry]()
    
            for dataitem in apiResponse.items {
                let entry = Entry(dataitem)
                entries.append(dataitem)
            }
            self.entries = entries
            self.reloadData()
        })
        task.resume()
        tasks.append(task)
    }
    

    首先,我们跟踪每个服务器请求(任务),这样当我们发起一个新请求时,我们可以取消任何未完成的请求,这样它们就不会浪费资源,也不会覆盖当前请求。有时,较旧的请求实际上可以在较新的请求之后完成,从而给您带来奇怪的结果。

    其次,我们将数据加载到服务器加载函数 (loadDataFromServer) 中的本地数组中,这样我们就不会更改数据的实时版本,因为我们仍在加载它。否则你会得到错误,因为项目和内容的数量可能会在数据的实际显示过程中发生变化,而 iOS 不喜欢这样并且会崩溃。数据完全加载后,我们将其加载到 UIViewController 中。我们通过使用 setter 使其更加优雅。

    self.entries = entries
    

    第三,你必须告诉 UICollectionView 你已经改变了你的数据,所以你必须调用 reloadData()。

    self.reloadData()
    

    第四,当您更新数据时使用 Dispatch 代码,因为您无法从后台线程更新 UI。

    决定从服务器加载数据的方式和时间取决于您和您的 UI 设计。您可以在 viewDidLoad() 中进行第一次调用。

    【讨论】:

    • 先生,这是一些非常有用且易于理解的信息。我会再看一遍,直到我完全理解为止。尽管我使用 Firebase 并且因此不处理 HTTPRequests,但我认为我仍然可以根据您的回答编辑我的代码。非常感谢!
    • 我怀疑你使用什么会很重要。您可以将“缓慢”的 firebase 请求写入一些更新控制器内容的代码中。
    • 目前还不能真正掌握它的窍门。现在尝试了几件事,但并没有按照我的预期去做——但我确信那是因为我对 Swift 还不够熟悉。我会做更多的研究并尽力而为。
    猜你喜欢
    • 2016-02-13
    • 2011-04-23
    • 1970-01-01
    • 2013-01-09
    • 1970-01-01
    • 1970-01-01
    • 2013-06-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多