Salva
Salva

Reputation: 807

How to show progress hud while compressing a file in Swift 4?

I'm using marmelroy/Zip framework to zip/unzip files in my project, and JGProgressHUD to show the progress of the operation.

I'm able to see the HUD if I try to show it from the ViewDidLoad method, but if I use it in the closure associated to the progress feature of the quickZipFiles method (like in the code sample), the hud is shown just at the end of the operation.

I guess this could be related to a timing issue, but since I'm not too much into completion handlers, closures and GDC (threads, asynchronous tasks, etc.) I would like to ask for a suggestion.

Any ideas?

// In my class properties declaration
var hud = JGProgressHUD(style: .dark)

// In my ViewDidLoad
self.hud.indicatorView = JGProgressHUDPieIndicatorView()
self.hud.backgroundColor = UIColor(white: 0, alpha: 0.7)

// In my method
do {
    self.hud.textLabel.text = NSLocalizedString("Zipping files...", comment: "Zipping File Message")
    self.hud.detailTextLabel.text = "0%"
    if !(self.hud.isVisible) {
        self.hud.show(in: self.view)
    }
    zipURL = try Zip.quickZipFiles(documentsList, fileName: "documents", progress: { (progress) -> () in
        let progressMessage = "\(round(progress*100))%"
        print(progressMessage)
        self.hud.setProgress(Float(progress), animated: true)
        self.hud.textLabel.text = NSLocalizedString("Zipping files...", comment: "Zipping File Message")
        self.hud.detailTextLabel.text = progressMessage
        if (progress == 1.0) {
            self.hud.dismiss()
        }
    })
} catch {
    print("Error while creating zip...")
}

Upvotes: 1

Views: 2868

Answers (2)

Thomas Zoechling
Thomas Zoechling

Reputation: 34253

ZIP Foundation comes with built-in support for progress reporting and cancelation.
So if you can switch ZIP library, this might be a better fit for your project. (Full disclosure: I am the author of this library)

Here's some sample code that shows how you can zip a directory and display operation progress on a JGProgressHUD. I just zip the main bundle's directory here as example.

The ZIP operation is dispatched on a separate thread so that your main thread can update the UI. The progress var is a default Foundation (NS)Progress object which reports changes via KVO.

import UIKit
import ZIPFoundation
import JGProgressHUD

class ViewController: UIViewController {

    @IBOutlet weak var progressLabel: UILabel!
    var indicator = JGProgressHUD()
    var isObservingProgress = false
    var progressViewKVOContext = 0

    @objc
    var progress: Progress?

    func startObservingProgress()
    {
        guard !isObservingProgress else { return }

        progress = Progress()
        progress?.completedUnitCount = 0
        self.indicator.progress = 0.0

        self.addObserver(self, forKeyPath: #keyPath(progress.fractionCompleted), options: [.new], context: &progressViewKVOContext)
        isObservingProgress = true
    }

    func stopObservingProgress()
    {
        guard isObservingProgress else { return }

        self.removeObserver(self, forKeyPath: #keyPath(progress.fractionCompleted))
        isObservingProgress = false
        self.progress = nil
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == #keyPath(progress.fractionCompleted) {
            DispatchQueue.main.async {
                self.indicator.progress = Float(self.progress?.fractionCompleted ?? 0.0)
                if let progressDescription = self.progress?.localizedDescription {
                    self.progressLabel.text = progressDescription
                }

                if self.progress?.isFinished == true {
                    self.progressLabel.text = ""
                    self.indicator.progress = 0.0
                }
            }
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }

    @IBAction func cancel(_ sender: Any) {
        self.progress?.cancel()
    }

    @IBAction func createFullArchive(_ sender: Any) {
        let directoryURL = Bundle.main.bundleURL

        let tempArchiveURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString).appendingPathExtension("zip")
        self.startObservingProgress()
        DispatchQueue.global().async {
            try? FileManager.default.zipItem(at: directoryURL, to: tempArchiveURL, progress: self.progress)
            self.stopObservingProgress()
        }
    }
}

Upvotes: 4

rmaddy
rmaddy

Reputation: 318824

Looking at the implementation of the zip library, all of the zipping/unzipping and the calls to the progress handlers are being done on the same thread. The example shown on the home page isn't very good and can't be used as-is if you wish to update the UI with a progress indicator while zipping or unzipping.

The solution is to perform the zipping/unzipping in the background and in the progress block, update the UI on the main queue.

Assuming you are calling your posted code from the main queue (in response to the user performing some action), you should update your code as follows:

// In my class properties declaration
var hud = JGProgressHUD(style: .dark)

// In my ViewDidLoad
self.hud.indicatorView = JGProgressHUDPieIndicatorView()
self.hud.backgroundColor = UIColor(white: 0, alpha: 0.7)

self.hud.textLabel.text = NSLocalizedString("Zipping files...", comment: "Zipping File Message")
self.hud.detailTextLabel.text = "0%"
if !(self.hud.isVisible) {
    self.hud.show(in: self.view)
}

DispatchQueue.global().async {
    defer {
        DispatchQueue.main.async {
            self.hud.dismiss()
        }
    }

    do {
        zipURL = try Zip.quickZipFiles(documentsList, fileName: "documents", progress: { (progress) -> () in
            DispatchQueue.main.async {
                let progressMessage = "\(round(progress*100))%"
                print(progressMessage)
                self.hud.setProgress(Float(progress), animated: true)
                self.hud.textLabel.text = NSLocalizedString("Zipping files...", comment: "Zipping File Message")
                self.hud.detailTextLabel.text = progressMessage
            }
        })
    } catch {
        print("Error while creating zip...")
    }
}

Upvotes: 2

Related Questions