Tuesday, 9 April 2024

Multiple Images Download and Load in Table View Async - Swift 5.5

Option 1: 

class
ImageDownloadOperation: Operation {

    let url: URL

    var image: UIImage?

    var completion: ((UIImage?) -> Void)?


    init(url: URL, completion: ((UIImage?) -> Void)? = nil) {

        self.url = url

        self.completion = completion

    }


    override func main() {

        guard !isCancelled else { return }


        // Download image data

        Task {

            do {

                let imageData = try await URLSession.shared.data(from: url)

                if let downloadedImage = UIImage(data: imageData.0) {

                    // Resize image to reduce memory usage

                    let scaledImage = resizeImage(downloadedImage, targetSize: CGSize(width: 100, height: 100))

                    image = scaledImage

                }

            } catch {

                print("Error downloading image: \(error)")

            }

            

        }

        

        // Call completion handler on the main thread

        DispatchQueue.main.async {

            self.completion?(self.image)

        }

    }


    private func resizeImage(_ image: UIImage, targetSize: CGSize) -> UIImage {

        let renderer = UIGraphicsImageRenderer(size: targetSize)

        return renderer.image { _ in

            image.draw(in: CGRect(origin: .zero, size: targetSize))

        }

    }

}


class ImageTableViewCell: UITableViewCell {

    @IBOutlet weak var customImageView: UIImageView!


    override func prepareForReuse() {

        super.prepareForReuse()

        customImageView.image = nil

    }

}


class ImageTableViewController: UITableViewController {

    let imageUrls = [

        URL(string: "https://example.com/image1.jpg")!,

        URL(string: "https://example.com/image2.jpg")!,

        // Add more image URLs as needed

    ]


    var images = [UIImage?]()


    override func viewDidLoad() {

        super.viewDidLoad()

        downloadImages()

    }


    func downloadImages() {

        Task {

            let downloadOperations = imageUrls.map { url in

                ImageDownloadOperation(url: url) { image in

                    self.updateUI(with: image)

                }

            }

            

            for operation in downloadOperations {

                operation.start()

            }

        }

    }


    func updateUI(with image: UIImage?) {

        DispatchQueue.main.async {

            self.images.append(image)

            self.tableView.reloadData()

        }

    }


    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return images.count

    }


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

        let cell = tableView.dequeueReusableCell(withIdentifier: "ImageCell", for: indexPath) as! ImageTableViewCell

        if let image = images[indexPath.row] {

            cell.customImageView.image = image

        } else {

            // Placeholder or loading indicator

            cell.customImageView.image = UIImage(named: "placeholder")

        }

        return cell

    }

}


OPTION 2:


This we can do using OPERATIONQUEUE. 
Here you can PAUSE, RESUME and CANCEL downloading. 


import UIKit


class ImageDownloadOperation1: Operation {

    let url: URL

    var image: UIImage?

    private var task: URLSessionDataTask?

    private var isExecutingInternal = false

    private var isFinishedInternal = false

    private var isPausedInternal = false

    private var isCancelledInternal = false


    init(url: URL) {

        self.url = url

        super.init()

    }


    override var isExecuting: Bool {

        return isExecutingInternal

    }


    override var isFinished: Bool {

        return isFinishedInternal

    }


    override var isAsynchronous: Bool {

        return true

    }


    override func start() {

        guard !isCancelled else { return }

        guard !isPausedInternal else { return }

        guard !isExecutingInternal else { return }


        isExecutingInternal = true

        task = URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in

            defer {

                self?.isExecutingInternal = false

                self?.isFinishedInternal = true

            }


            guard let data = data, let image = UIImage(data: data), error == nil else { return }

            self?.image = image

        }

        task?.resume()

    }


    override func cancel() {

        super.cancel()

        task?.cancel()

        isCancelledInternal = true

    }


    func pause() {

        isPausedInternal = true

        task?.suspend()

    }


    func resume() {

        isPausedInternal = false

        task?.resume()

    }

}



class UsingOperationQueueClass: UIViewController {

    // Usage

    let operationQueue = OperationQueue()

    let imageUrls = [

        URL(string: "https://example.com/image1.jpg")!,

        URL(string: "https://example.com/image2.jpg")!,

        // Add more image URLs as needed

    ]


    var images = [UIImage?]()


    override func viewDidLoad() {

        super.viewDidLoad()

        // Create ImageDownloadOperation instances for each image URL

        // Here you can modify the code by adding completion bloack and load image.

        // Same like Option 1 code.

        let operations = imageUrls.map { ImageDownloadOperation1(url: $0) }


        // Add operations to the queue

        operationQueue.addOperations(operations, waitUntilFinished: false)


        // Pause the 2nd operation

        operations[1].pause()


        // Resume other operations

        operations.filter { $0 !== operations[1] }.forEach { $0.resume() }


        // Resume the 2nd operation

        operations[1].resume()

    }

}



Setting Up Multiple App Targets in Xcode from a Single Codebase

 To create two different apps (like "Light" and "Regular") from the same codebase in Xcode, you can follow these steps b...