对象包含从服务器拍摄的照片应重新加载包含对象的 tableView

Objects contain photo taken from server should reload tableView containing the objects

我在 ViewController 中有一个 table 视图,其中包含一个对象数组 Channel;该数组由保存在我的 CoreData 数据库

中的 json 字符串填充

解析 json 并创建对象 Channel 我可以拥有典型的变量,如 id、频道名称和对话成员;这是对象 Channel

class Channel: NSObject {
    var id: String?
    var name: String?
    var members = [Member]()

    var profileImage: UIImage?

    override init() {
       super.init()
    }

    init(id: String, name: String, members: [String]) {
        super.init()
        self.id = id
        self.name = name
        for idMember in members {
            let newMember = Member(id: idMember)
            self.members.append(newMember)
        }
    }

    func loadPhoto(id: Int, completion: @escaping (Bool) -> Void) {
        Helper.getImage(id: id) { (imageResult) in
            if let image = imageResult {
                self.profileImage = image
                completion(true)
            } else {
                completion(false)
            }
        }
    }
}

变量 profileImage 应该取自具有 API 的服务器,问题是当创建对象时解析函数 getDataFromCoreData() 中的 json Channel 数组中的对象具有变量 id、名称和成员,但仍然没有图像,因此我的 tableView 将具有各种通道的行,但没有 profileImage,因为异步调用.

在我的例子中,为了重新加载 table 以可视化图像,一个函数 loadImages() 等待异步调用完成并重新加载 tableView

import UIKit
import Firebase

class ConversationsViewController: UIViewController {

    var channelList = [Channel]()

    override func viewDidLoad() {
        super.viewDidLoad()
        //I get the channelList from a JSON saved like a string in my coreData with a custom function
        channelList = getDataFromCoreData()
        loadImages()
    }

    func loadImages() {
        for channel in self.channelList {
            channel.loadPhoto(id: channel.id) { success in
                if success {
                    self.chatTable.reloadData()
                }
            }
        }
    }
}
extension ConversationsViewController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MessageChannelTableViewCell

        var channel = channelList[indexPath.row]

        cell.titleLabel.text = channel.name

        if let profileImage = channel.profileImage {
            cell.profileImage.image = profileImage
        } else {
            if channel.members.count > 2 {
                cell.profileImage.image = theme.groupChatImage
            } else {
                cell.profileImage.image = UIImage(named: "profile-icon")!
            }
        }

        return cell
    }

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

我知道这个问题可能很长很无聊,但我想知道是否有更好的方法,如果有,你可以告诉我改进我的代码

也许您可以每次都重新加载单个单元格而不是整个 UITableView:

你应该想办法获取每个需要下载图片的单元格的indexPathRow然后:

let indexPosition = IndexPath(row: indexPathRow, section: 0)
tableView.reloadRows(at: [indexPosition], with: .none)

...查看动画的枚举选项以选择适合您应用的选项。

编辑:您可以在 CellForRowAtIndexPath 中测试图像是否为 nil,然后从您的服务器加载它。在成功闭包中重新加载单元格本身......你已经有了 indexPath。使用 weak self 来避免保留循环。

有很多扩展 UIImageView 的库来解决这个问题:

AlamofireImage Nuke SDWebImage KingFisher

在架构上它们与您的解决方案不同的地方是 1) 有一个单独的图像缓存持久保存到磁盘(有时是 NSCache,有时不是),它可以防止重复下载相同的图像,而且还可以让您不必将所有内容存储在数据库(您只需存储 URL)。 2) 扩展在 UIImageView 上,因此图像视图知道如何加载 URL 并在图像视图在完成之前被销毁时取消请求。它们通常提供额外的功能,例如在下载期间使用的占位符图像、调整图像大小等。编写自己的缓存并不难,尽管我几乎总是只使用 pods 之一。这是我为我不想使用 pod 的演示项目编写的一个非常简单的缓存:

class ImageCache {
    enum Result {
        case success(UIImage, URL), failure (Error)
    }
    static let shared = ImageCache()
    private let cache = NSCache<NSURL, UIImage>()
    private init() { }
    func image(for url: URL, completion: @escaping (Result) -> ()) {
        guard let image = cache.object(forKey: url as NSURL) else {
            DispatchQueue.global(qos: .userInitiated).async { [weak self] in
                guard let `self` = self,
                      let image = UIImage.gif(url: url.absoluteString) else {
                    completion(.failure(NSError()))
                    return
                }
                self.cache.setObject(image, forKey: url as NSURL)
                completion(.success(image, url))
            }
            return
        }
        completion(.success(image, url))
    }
}