I've tried creating my own ProgressView
to support iOS 13, but for some reason it appears to not work. I've tried @State
, @Binding
and the plain var progress: Progress
, but it doesn't update at all.
struct ProgressBar: View {
@Binding var progress: Progress
var body: some View {
VStack(alignment: .leading) {
Text("\(Int(progress.fractionCompleted))% completed")
ZStack {
RoundedRectangle(cornerRadius: 2)
.foregroundColor(Color(UIColor.systemGray5))
.frame(height: 4)
GeometryReader { metrics in
RoundedRectangle(cornerRadius: 2)
.foregroundColor(.blue)
.frame(width: metrics.size.width * CGFloat(progress.fractionCompleted))
}
}.frame(height: 4)
Text("\(progress.completedUnitCount) of \(progress.totalUnitCount)")
.font(.footnote)
.foregroundColor(.gray)
}
}
}
In my content view I added both the iOS 14 variant and the iOS 13 supporting one. They look the same, but the iOS 13 variant does not change anything.
struct ContentView: View {
@State private var progress = Progress(totalUnitCount: 10)
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
VStack {
ProgressBar(progress: $progress)
.padding()
.onReceive(timer) { timer in
progress.completedUnitCount += 1
if progress.isFinished {
self.timer.upstream.connect().cancel()
progress.totalUnitCount = 500
}
}
if #available(iOS 14, *) {
ProgressView(progress)
.padding()
.onReceive(timer) { timer in
progress.completedUnitCount += 1
if progress.isFinished {
self.timer.upstream.connect().cancel()
progress.totalUnitCount = 500
}
}
}
}
}
}
The iOS 14 variant works, but my iOS 13 implementation fails. Can somebody help me?
Progress is-a NSObject, it is not a struct, so state does not work for it. You have to use KVO to observe changes in Progress and redirect into SwiftUI view's source of truth.
Here is a simplified demo of possible solution. Tested with Xcode 12.1 / iOS 14.1
class ProgressBarViewModel: ObservableObject {
@Published var fractionCompleted: Double
let progress: Progress
private var observer: NSKeyValueObservation!
init(_ progress: Progress) {
self.progress = progress
self.fractionCompleted = progress.fractionCompleted
observer = progress.observe(\.completedUnitCount) { [weak self] (sender, _) in
self?.fractionCompleted = sender.fractionCompleted
}
}
}
struct ProgressBar: View {
@ObservedObject private var vm: ProgressBarViewModel
init(_ progress: Progress) {
self.vm = ProgressBarViewModel(progress)
}
var body: some View {
VStack(alignment: .leading) {
Text("\(Int(vm.fractionCompleted * 100))% completed")
ZStack {
RoundedRectangle(cornerRadius: 2)
.foregroundColor(Color(UIColor.systemGray5))
.frame(height: 4)
GeometryReader { metrics in
RoundedRectangle(cornerRadius: 2)
.foregroundColor(.blue)
.frame(width: metrics.size.width * CGFloat(vm.fractionCompleted))
}
}.frame(height: 4)
Text("\(vm.progress.completedUnitCount) of \(vm.progress.totalUnitCount)")
.font(.footnote)
.foregroundColor(.gray)
}
}
}
and updated call place to use same constructor
struct ContentView: View {
@State private var progress = Progress(totalUnitCount: 10)
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
VStack {
ProgressBar(progress) // << here !!
// ... other code