Thanks for sharing your example. I created a minimal example that shows the behavior I describe.
ContentView:
//
// ContentView.swift
// ProgressViewDemo
//
//
import SwiftUI
struct ContentView: View {
@State var copier: ServerCopyProvider!
@State var isCopying: Bool = false
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
.onDrop(of: [.audio, .folder], isTargeted: nil, perform: copyDraggedFiles)
Text("Hello, world!")
if isCopying {
ProgressView(value: copier.copyProgress.fractionCompleted, total: 1)
.progressViewStyle(.circular)
}
}
.padding()
}
func copyDraggedFiles(_ providers: [NSItemProvider], _ point: CGPoint) -> Bool {
copier = ServerCopyProvider(providers, withAction: { url, error in
Task {
try? await Task.sleep(for: .seconds(1))
}
})
isCopying = true
copier.copyToServer()
return true
}
}
#Preview {
ContentView()
}
ServerCopyProvider
// ServerCopyProvider.swift
// MusicConnect
//
//
import Foundation
class ServerCopyProvider: ObservableObject {
@Published var copyProgress: Progress
let fileAction: (URL?, Error?) -> Void
let providers: [NSItemProvider]
let LOADUNITCOUNT: Int64 = 1
let TRANSFERUNITCOUNT: Int64 = 10
init(_ urls: [URL], withAction: @escaping (URL?, Error?) -> Void) {
let units = Int64(urls.count) * (LOADUNITCOUNT + TRANSFERUNITCOUNT)
self.copyProgress = Progress(totalUnitCount: units)
self.fileAction = withAction
var newProviders: [NSItemProvider] = []
for url in urls {
if let provider = NSItemProvider(contentsOf: url) {
provider.suggestedName = url.lastPathComponent
newProviders.append(provider)
}
}
self.providers = newProviders
}
init(_ providers: [NSItemProvider], withAction: @escaping (URL?, Error?) -> Void) {
let units = Int64(providers.count) * (LOADUNITCOUNT + TRANSFERUNITCOUNT)
self.copyProgress = Progress(totalUnitCount: units)
self.fileAction = withAction
self.providers = providers
}
func copyToServer() {
for provider in self.providers {
provideFileAction(provider, to: fileAction)
}
}
// MARK: - Private functions
fileprivate func provideFileAction(_ provider: NSItemProvider, to fileAction: @escaping (URL?, Error?) -> Void) {
let providerIds = ["public.audio", "public.folder", "Public.url"]
for providerId in providerIds {
if provider.hasItemConformingToTypeIdentifier(providerId) {
let transferProgress = Progress(totalUnitCount: TRANSFERUNITCOUNT)
copyProgress.addChild(transferProgress, withPendingUnitCount: TRANSFERUNITCOUNT)
let loadProgress = provider.loadFileRepresentation(forTypeIdentifier: providerId, completionHandler: self.urlAction(withProgress: transferProgress))
copyProgress.addChild(loadProgress, withPendingUnitCount: LOADUNITCOUNT)
return
}
}
}
fileprivate func urlAction(withProgress: Progress) -> ((URL?, Error?) -> Void) {
return { url, error in
self.fileAction(url, error)
withProgress.completedUnitCount = self.TRANSFERUNITCOUNT
print("\(withProgress.fractionCompleted) - \(self.copyProgress.fractionCompleted)")
}
}
}
If you compile it and e.g. drop two files on the globe. The output of the print statement yields:
1.0 - 0.5454545454545454
1.0 - 1.0
But the ProgressView remains at where it was when the progress was updated by the NSItemProvider.