Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIProgressView with multiple progress

How can I build a progress bar with multiple progress in ios ? I don't think it is possible with the UIProgressView

I am looking for something like this(UI only) : progress bar Thanks !

like image 458
AlexB Avatar asked Oct 15 '17 22:10

AlexB


1 Answers

The standard UIProgressView shows only one progress and if you're trying to capture the progress of multiple items, you will have a hierarchy of Progress/NSProgress objects. E.g. if uploading five files, you have five Progress objects, one for each upload, and then have a sixth NSProgress, which the UIProgressView observes, which has the other five as children. See Creating a Tree of Progress Objects in the NSProgress class reference. See https://stackoverflow.com/a/36616538/1271826 for an example.

If you want a progress view that visually represents the respective progress of individual NSProgress, you'll have to do this yourself (or find a third party control that does this). But the standard UIProgressViewwill not do this for you.

But it's not hard. You can, for example, create a horizontal stack view, add one UIProgressView for each NSProgress that you want to track, set the respective widths of the UIProgressView subviews to be, for example, whatever percentage they represent of the overall tasks.

E.g. here is a horizontal stack view with three UIProgressView arranged subviews:

enter image description here

Having said that, I prefer the standard single, consolidated UIProgessView because the user can more easily see how far along they are. If you have a series of progress views like this, it's really hard to visually see "how much longer do I have" because you have to look at all the individual progress views and mentally figure it out (e.g. "the first looks like it's 50% done, the second is 66% done, the third is 75% done ... so exactly how far along is the whole thing?!?").

Anyway, here my Swift code to render the above. I know you're looking for Objective-C, but hopefully this is enough to illustrate the basic idea that can easily be implemented in either Swift or Objective-C:

class MultiProgressView: UIStackView {

    struct ProgressInfo {
        let progressView: UIProgressView
        let totalToken: NSKeyValueObservation
        let progressToken: NSKeyValueObservation
    }
    var progressInfo = [ProgressInfo]()
    var progressHandler: ((Double) -> Void)?

    var widthConstraints: [NSLayoutConstraint]?

    func add(_ progress: Progress, progressTintColor: UIColor = .blue, trackTintColor: UIColor? = nil) {
        let progressView = UIProgressView(progressViewStyle: .bar)
        progressView.translatesAutoresizingMaskIntoConstraints = false
        progressView.progressTintColor = progressTintColor
        progressView.trackTintColor = trackTintColor ?? progressTintColor.withAlphaComponent(0.5)
        addArrangedSubview(progressView)
        progressView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1)
        progressView.observedProgress = progress
        let totalToken = progress.observe(\.totalUnitCount) { [weak self] (_, _) in
            self?.updateWidths()
        }
        let progressToken = progress.observe(\.completedUnitCount) { [weak self] (_, _) in
            if let percent = self?.percent {
                self?.progressHandler?(percent)
            }
        }
        progressInfo.append(ProgressInfo(progressView: progressView, totalToken: totalToken, progressToken: progressToken))
        updateWidths()
    }

    private func updateWidths() {
        if let widthConstraints = self.widthConstraints {
            removeConstraints(widthConstraints)
        }

        let totalUnitCount = self.totalUnitCount
        guard totalUnitCount > 0 else { return }

        widthConstraints = progressInfo.map { progressInfo in
            let unitCount = progressInfo.progressView.observedProgress?.totalUnitCount ?? 0
            return NSLayoutConstraint(item: progressInfo.progressView, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: CGFloat(unitCount) / CGFloat(totalUnitCount), constant: 0)
        }

        DispatchQueue.main.async {
            self.addConstraints(self.widthConstraints!)
        }
    }

    var totalUnitCount: Int64 {
        return progressInfo.reduce(Int64(0)) { $0 + ($1.progressView.observedProgress?.totalUnitCount ?? 0) }
    }

    var completedUnitCount: Int64 {
        return progressInfo.reduce(Int64(0)) { $0 + ($1.progressView.observedProgress?.completedUnitCount ?? 0) }
    }

    var percent: Double {
        return Double(completedUnitCount) / Double(totalUnitCount)
    }
}

And then to use it:

override func viewDidLoad() {
    super.viewDidLoad()

    let download1 = Download(...)
    let download2 = Download(...)
    let download3 = Download(...)

    mainProgressView.progressHandler = { [weak self] percent in
        self?.checkIfDone(percent: percent)
    }

    mainProgressView.add(download1.progress, progressTintColor: .blue)
    mainProgressView.add(download2.progress, progressTintColor: .green)
    mainProgressView.add(download3.progress, progressTintColor: .red)

    download1.startDownload()
    download2.startDownload()
    download3.startDownload()
}

private func checkIfDone(percent: Double) {
    if percent == 1 {
        print("done")
    }
}
like image 145
Rob Avatar answered Nov 10 '22 20:11

Rob