我是iOS开发的初学者,我想制作一个Instagram克隆应用,在制作Instagram克隆应用的新闻源时遇到问题。
所以我使用Firebase来存储图像和数据库。发布图像(将数据上传到Firebase)后,我想使用我的firebase中上传的数据填充表格视图。
但是当我运行应用程序时,我的故事板中的虚拟图像和标签与我放在表格视图中的下载数据重叠。在我向下滚动后,我下载的数据最终会显示出来。
这是我运行应用程序时的gif:
http://g.recordit.co/iGIybD9Pur.gif
.gif中有3个用户显示
从Firebase异步下载图像后(在加载指示灯解除后),我预计MegawatiRI将显示在桌面的顶部,但是虚拟将首先显示,但在我向下滚动并返回顶部之后, MegawatiRI最终会出现。
我相信MegawatiRI已成功下载,但我不知道为什么虚拟图像看起来与实际数据重叠。当我的应用运行时,我不希望虚拟对象显示。
以下是原型单元格的屏幕截图:
这是表视图控制器的简化代码:
class NewsFeedTableViewController: UITableViewController {
var currentUser : User!
var media = [Media]()
override func viewDidLoad() {
super.viewDidLoad()
tabBarController?.delegate = self
// to set the dynamic height of table view
tableView.estimatedRowHeight = StoryBoard.mediaCellDefaultHeight
tableView.rowHeight = UITableViewAutomaticDimension
// to erase the separator in the table view
tableView.separatorColor = UIColor.clear
}
override func viewWillAppear(_ animated: Bool) {
// check wheter the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
RealTimeDatabaseReference.users(uid: user.uid).reference().observeSingleEvent(of: .value, with: { (snapshot) in
if let userDict = snapshot.value as? [String:Any] {
self.currentUser = User(dictionary: userDict)
}
})
} else {
// user not logged in
self.performSegue(withIdentifier: StoryBoard.showWelcomeScreen, sender: nil)
}
}
tableView.reloadData()
fetchMedia()
}
func fetchMedia() {
SVProgressHUD.show()
Media.observeNewMedia { (mediaData) in
if !self.media.contains(mediaData) {
self.media.insert(mediaData, at: 0)
self.tableView.reloadData()
SVProgressHUD.dismiss()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: StoryBoard.mediaCell, for: indexPath) as! MediaTableViewCell
cell.currentUser = currentUser
cell.media = media[indexPath.section]
// to remove table view highlight style
cell.selectionStyle = .none
return cell
}
}
以下是表格视图单元格的简化代码:
class MediaTableViewCell: UITableViewCell {
var currentUser: User!
var media: Media! {
didSet {
if currentUser != nil {
updateUI()
}
}
}
var cache = SAMCache.shared()
func updateUI () {
// check, if the image has already been downloaded and cached then just used the image, otherwise download from firebase storage
self.mediaImageView.image = nil
let cacheKey = "\(self.media.mediaUID))-postImage"
if let image = cache?.object(forKey: cacheKey) as? UIImage {
mediaImageView.image = image
} else {
media.downloadMediaImage { [weak self] (image, error) in
if error != nil {
print(error!)
}
if let image = image {
self?.mediaImageView.image = image
self?.cache?.setObject(image, forKey: cacheKey)
}
}
}
那么是什么让虚拟图像与我下载的数据重叠?
答案 0 :(得分:1)
出现虚拟图像是因为您的表视图控制器在tableViewController上正确设置当前用户之前开始渲染单元格。
因此,在第一次调用cellForRowAtIndexPath时,您的控制器中可能有一个nil currentUser,它将被传递给单元格。因此,单元类中的didSet属性观察者不会调用updateUI():
didSet {
if currentUser != nil {
updateUI()
}
}
稍后,您重新加载数据并且现在已经设置了当前用户,因此事情开始按预期工作。
来自updateUI()的这一行应隐藏您的虚拟图像。但是,并不总是按上面所述调用updateUI:
self.mediaImageView.image = nil
我真的没有看到为什么updateUI需要当前用户不为零的原因。所以你可以在didSet观察器中消除nil测试,并且总是调用updateUI:
var media: Media! {
didSet {
updateUI()
}
或者,您可以重新排列表视图控制器,以便在加载数据源之前实际等待设置当前用户。 viewWillAppear中与登录相关的代码具有嵌套的完成错误以设置当前用户。这些可能是异步执行的..因此你要么必须等待它们完成或处理当前用户为零。
Auth.auth etc {
// completes asynchronously, setting currentUser
}
// Unless you do something to wait, the rest starts IMMEDIATELY
// currentUser is not set yet
tableView.reloadData()
fetchMedia()
(1)我认为在图像下载并插入共享缓存时重新加载单元格(使用reloadRows)是一种很好的形式。您可以参考此question中的答案,了解从单元格启动的异步任务如何使用NotificationCenter或委派联系tableViewController。
(2)我怀疑你的图像下载任务目前正在主线程中运行,这可能不是你想要的。当你解决这个问题时,你将需要切换回主线程来更新图像(正如你现在所做的那样)或重新加载行(如上所述)。
答案 1 :(得分:0)
在主线程中更新您的UI。
if let image = image {
DispatchQueue.main.async {
self?.mediaImageView.image = image
}
self?.cache?.setObject(image, forKey: cacheKey)
}