UITableView单元格中的初始滚动延迟具有调整大图的大小?

时间:2017-04-29 23:56:47

标签: ios swift uitableview

我基本上和这个问题有同样的问题:

Slow scroll on UITableView images

我的UITableView包含UIImageView,尺寸为87x123。

当我加载UITableViewController时,它首先调用一个循环遍历图像数组的函数。这些图像是从照片库存储的高分辨率。在每次迭代中,它检索图像并将每个图像调整为87x123,然后将其存储回数组中的原始图像。

当所有图像都已调整大小并存储后,它会调用self.tableView.reloadData将数组中的数据填充到单元格中。

然而,就像上面提到的问题一样,如果我在调整所有图像大小并将其存储在数组中之前快速滚动,我的UITablView就会出现波动和滞后。

这是有问题的代码:

extension UIImage
{
    func resizeImage(originalImage: UIImage, scaledTo size: CGSize) -> UIImage
    {
        // Avoid redundant drawing
        if originalImage.size.equalTo(size)
        {
            return originalImage
        }

        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        originalImage.draw(in: CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(size.width), height: CGFloat(size.height)))

        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        return image
    }
}

func loadImages()
{   
    DispatchQueue.global(qos: .background).async {

        for index in 0..<self.myArray.count
        {
            if let image = self.myArray[index].image
            {
                self.myArray[index].image = image.resizeImage(originalImage: image, scaledTo: CGSize(width: 87, height: 123) )
            }

            if index == self.myArray.count - 1
            {
                print("FINISHED RESIZING ALL IMAGES")
            }
        }
    }

    tableView.reloadData()
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    ...

    // Size is 87x123
    let thumbnailImage = cell.viewWithTag(1) as! UIImageView

    DispatchQueue.main.async{

        thumbnailImage.image = self.myArray[indexPath.row]

    }

    thumbnailImage.contentMode = UIViewContentMode.scaleAspectFill

    thumbnailImage.layer.borderColor = UIColor.black.cgColor
    thumbnailImage.layer.borderWidth = 1.0
    thumbnailImage.clipsToBounds = true

    return cell
}

我知道在后台线程中执行任何非UI操作,这就是我所做的。然后我在cellForRowAt中所做的就是使用indexPath.row将图像加载到单元格中。

问题是,如前所述,如果我开始滚动UITableView BEFORE FINISHED RESIZING ALL IMAGES打印出来,即在所有图片调整大小之前,有显着滞后和缓慢。

但是,如果我等待UNTIL所有图像都已调整大小并且在滚动FINISHED RESIZING ALL IMAGES之前调用UITableView,则滚动平滑而没有任何滞后。

我可以放置一个加载指示器并让用户等到所有图像都已调整大小并加载到单元格中,然后再进行用户交互,但这会很烦人,因为调整所有高分辨率图像大约需要8秒钟(18张图片要调整大小)。

有没有更好的方法可以解决这个滞后问题?

更新:关注@ iWheelBuy的第二个例子,我实施了以下内容:

final class ResizeOperation: Operation {

    private(set) var image: UIImage
    let index: Int
    let size: CGSize

    init(image: UIImage, index: Int, size: CGSize) {
        self.image = image
        self.index = index
        self.size = size
        super.init()
    }

    override func main() {
        image = image.resizeImage(originalImage: image, scaledTo: size)
    }
}

class MyTableViewController: UITableViewController
{
    ...

    lazy var resizeQueue: OperationQueue = self.getQueue()

    var myArray: [Information] = []

    internal struct Information
    {
        var title: String?
        var image: UIImage?

        init()
        {

        }
    }

    override func viewDidLoad()
    {
        ....

        loadImages()

        ...
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        ...

        // Size is 87x123
        let thumbnailImage = cell.viewWithTag(1) as! UIImageView

        DispatchQueue.main.async{

            thumbnailImage.image = self.myArray[indexPath.row].image

        }

        thumbnailImage.contentMode = UIViewContentMode.scaleAspectFill

        return cell
    }

    func loadImages()
    {
        let size = CGSize(width: 87, height: 123)

        for item in myArray
        {
            let operations = self.myArray.enumerated().map({ ResizeOperation(image: item.image!, index: $0.offset, size: size) })

            operations.forEach { [weak queue = resizeQueue, weak controller = self] (operation) in
                operation.completionBlock = { [operation] in
                    DispatchQueue.main.async { [image = operation.image, index = operation.index] in

                        self.update(image: image, index: index)
                    }
                }
                queue?.addOperation(operation)
            }
        }
    }

    func update(image: UIImage, index: Int)
    {
        myArray[index].image = image

        tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: UITableViewRowAnimation.fade)

    }    
}

但是,在调用tableView.reloadRows时,我收到错误消息:

  

尝试从第0部分删除第0行,但只有0部分   在更新之前

我对这意味着什么以及如何解决它感到有些困惑。

1 个答案:

答案 0 :(得分:2)

很难确定你有滞后的原因。但是有一些想法可能会帮助您提高代码的性能。

尝试在开始时使用一些小图像,如果它是影响性能不佳的原始图像的大小,请参阅。同时尝试隐藏这些行,看看是否有任何变化:

//    thumbnailImage.contentMode = UIViewContentMode.scaleAspectFill
//    thumbnailImage.layer.borderColor = UIColor.black.cgColor
//    thumbnailImage.layer.borderWidth = 1.0
//    thumbnailImage.clipsToBounds = true

您的代码有点老了,for loadImages循环只需几行代码就可以通过将mapforEach应用于您的图片来提高可读性阵列。

另外,关于你的图像数组。您从主线程中读取它并从后台线程修改它。你同时做到了。我建议只在背景上调整图片大小...除非你确定不会有不良后果

检查下面的代码示例#1,了解当前代码的外观。

另一方面,你可以采取其他方式。例如,您可以在启动时设置一些占位符图像,并在某个特定单元格的图像准备就绪时更新单元格。不是所有的图像一次!如果您使用某个串行队列,您将每0.5秒获得一次图像更新,并且UI更新将会很好。

检查代码示例#2。它没有经过测试,只是为了展示你可以走的路。

顺便说一下,您是否尝试过将QualityOfService从background更改为userInitiated?它可能会减少调整时间......或者不是(:

DispatchQueue.global(qos: .userInitiated).async {
    // code
}

示例#1

extension UIImage {

    func resize(to size: CGSize) -> UIImage {
        guard self.size.equalTo(size) else { return self }
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        draw(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }

    static func resize(images: [UIImage], size: CGSize, completion: @escaping ([UIImage]) -> Void) {
        DispatchQueue.global(qos: .background).async {
            let newArray = images.map({ $0.resize(to: size) })
            DispatchQueue.main.async {
                completion(newArray)
            }
        }
    }
}

final class YourController: UITableViewController {

    var myArray: [UIImage] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        self.loadImages()
    }
}

fileprivate extension YourController {

    func loadImages() {
        UIImage.resize(images: myArray, size: CGSize(width: 87, height: 123)) { [weak controller = self] (newArray) in
            guard let controller = controller else { return }
            controller.myArray = newArray
            controller.tableView.reloadData()
        }
    }
}

示例#2

final class ResizeOperation: Operation {

    private(set) var image: UIImage
    let index: Int
    let size: CGSize

    init(image: UIImage, index: Int, size: CGSize) {
        self.image = image
        self.index = index
        self.size = size
        super.init()
    }

    override func main() {
        image = image.resize(to: size)
    }
}

final class YourController: UITableViewController {

    var myArray: [UIImage] = []
    lazy var resizeQueue: OperationQueue = self.getQueue()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.loadImages()
    }

    private func getQueue() -> OperationQueue {
        let queue = OperationQueue()
        queue.qualityOfService = .background
        queue.maxConcurrentOperationCount = 1
        return queue
    }
}

fileprivate extension YourController {

    func loadImages() {
        let size = CGSize(width: 87, height: 123)
        let operations = myArray.enumerated().map({ ResizeOperation(image: $0.element, index: $0.offset, size: size) })
        operations.forEach { [weak queue = resizeQueue, weak controller = self] (operation) in
            operation.completionBlock = { [operation] in
                DispatchQueue.main.async { [image = operation.image, index = operation.index] in
                    controller?.update(image: image, index: index)
                }
            }
            queue?.addOperation(operation)
        }
    }

    func update(image: UIImage, index: Int) {
        myArray[index] = image
        tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: UITableViewRowAnimation.fade)
    }
}