仅在加载所有数据时加载CollectionView单元

时间:2018-09-30 11:44:57

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

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

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

我以前遇到的问题:我从loadData()内的帖子中加载了数据,然后将基于保存在posts数组中的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时,它确实会返回值。因此,我的有根据的猜测是数据没有及时获取。我现在正在寻找一种仅在posts数组和用户数组加载时调用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隐藏几秒钟,但这都取决于连接速度等。因此,我认为应该有一个更合适的解决方案。

欢迎任何有用的想法!

4 个答案:

答案 0 :(得分:1)

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

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

我希望它能解决您的问题。

答案 1 :(得分:0)

您问了很多事情。是的,的确,在显示视图时可能不会加载数据。幸运的是,在设计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中。我们通过使用二传手使此操作更加优雅。

self.entries = entries

第三,您必须指示UICollectionView您更改了数据,因此必须调用reloadData()。

self.reloadData()

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

如何以及何时决定从服务器加载数据取决于您和UI设计。您可以在viewDidLoad()中进行首次调用。

答案 2 :(得分:0)

您可以从情节提要中实现此目的,而无需加入collectionview的委托和数据源。在控制器类中,当您获取数据时,只需设置collectionview.delegate = self和数据源,然后重新加载该集合视图即可。

答案 3 :(得分:0)

在这种情况下我会做什么

  1. 请勿将collectionview的委托设置为您的控制器。
  2. 执行Firebase请求1将数据加载到array1中。 在第一个请求的完成中,调用另一个执行request2的函数,以将数据提取并加载到数组2中。
  3. 在第二个请求的完成处理程序中,设置委托并重新加载数据(在主线程中)

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

有关如何操作的详细信息,请参见此Helpful answer

希望有帮助