Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Adding children to a Progress instance is easy, but removing is not possible by default. This could have been useful when you want to use a single Progress instance which can have different children over time. Using this custom class MutableProgress makes this possible. See: https://www.avanderlee.com/general/controlling-progress-children/
import UIKit
/// An additional class build on top of `Progress` to make it possible to also remove children from progress.
public final class MutableProgress: Progress {
public override var totalUnitCount: Int64 {
get {
return Int64(children.count)
}
set {
fatalError("Setting the total unit count is not supported for MutableProgress")
}
}
public override var completedUnitCount: Int64 {
get {
return Int64(children.filter { $0.key.isCompleted }.count)
}
set {
fatalError("Setting the completed unit count is not supported for MutableProgress")
}
}
public override var fractionCompleted: Double {
return children.map { $0.key.fractionCompleted }.reduce(0, +) / Double(totalUnitCount)
}
/// All the current tracked children.
private var children: [Progress: NSKeyValueObservation] = [:]
/// Adds a new child. Will always use a pending unit count of 1.
///
/// - Parameter child: The child to add.
func addChild(_ child: Progress) {
willChangeValue(for: \.totalUnitCount)
children[child] = child.observe(\.fractionCompleted) { [weak self] (progress, _) in
self?.willChangeValue(for: \.fractionCompleted)
self?.didChangeValue(for: \.fractionCompleted)
if progress.isCompleted {
self?.willChangeValue(for: \.completedUnitCount)
self?.didChangeValue(for: \.completedUnitCount)
}
}
didChangeValue(for: \.totalUnitCount)
}
/// Removes the given child from the progress reporting.
///
/// - Parameter child: The child to remove.
func removeChild(_ child: Progress) {
willChangeValue(for: \.fractionCompleted)
willChangeValue(for: \.completedUnitCount)
willChangeValue(for: \.totalUnitCount)
children.removeValue(forKey: child)?.invalidate()
didChangeValue(for: \.totalUnitCount)
didChangeValue(for: \.completedUnitCount)
didChangeValue(for: \.fractionCompleted)
}
// MARK: Overriding methods to make sure this class is used correctly.
public override func addChild(_ child: Progress, withPendingUnitCount inUnitCount: Int64) {
assert(inUnitCount == 1, "Unit count is ignored and is fixed to 1 for MutableProgress")
addChild(child)
}
}
private extension Progress {
var isCompleted: Bool {
guard totalUnitCount > 0 else { return true }
return completedUnitCount >= totalUnitCount
}
}
@CPiersigilli

This comment has been minimized.

Copy link

CPiersigilli commented Jul 29, 2018

When testing with your beautiful MutableProgress, I realized that it does not respond to the deletion of processes (for example, totalUploadProgress.cancel()): can you explain why? How can I modify your MutableProgress to make also work .cancel(), .pause() and .resume().
Thank you.
Cesare Piersigilli

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.