In my app, I am uploading a file to my server from my ObservableObject (HTTPHandler). The published strings from HTTPHandler are used by the view (UploadTemplate) to convey status and errors.
HTTPHandler, which also handles all my HTTP needs, is obviously thrown off the main queue.
So essentially the view UploadTemplate has Text views which use the string from HTTPHandler.statusMsg to keep the user informed of progress.
I embedded every change of HTTPHandler.statusMsg within a DispatchQueue.main.async yet I still get the warning:
Publishing changes from background threads is not allowed; make sure
to publish values from the main thread (via operators like
receive(on:)) on model updates.
I thought perhaps it’s the initial setting of statusMsg when HTTPHandler is initialized perhaps? All my attempts at figuring this out has led me nowhere with the purple warning.
I’ve double checked that statusMsg is only within HTTPHandler, and that they’re all embedded within a DispatchQueue.main.async call.
Any idea what I might be doing wrong?
import SwiftUI
struct UploadTemplate: View {
@StateObject var xFile = XFile()
@EnvironmentObject var xUser : XUser
@State var isShowingDocumentPicker = false
@State var error = "no error"
@State var isPerformingURL = false
@State var currentStatus = ""
@StateObject var httpHandler = HTTPHandler()
var body: some View {
NavigationStack {
ZStack {
Color.teal
.ignoresSafeArea()
VStack{
Text(xFile.fileName.isEmpty ? "No File Selected" : xFile.fileName)
Text(httpHandler.statusMsg)
Text(httpHandler.errorMsg)
Button {
isShowingDocumentPicker = true
} label: {
Text("Select File")
}
.disabled(isPerformingURL)
Button {
Task {
isPerformingURL = true
xFile.fileStage = .uploadOriginal
let request = xFile.makePostRequest(xUser: xUser, script: "uploadOrig.pl")
var result = await httpHandler.asyncCall(request: request, xFile: xFile)
}
}
label: {
Text("Upload")
}
.disabled((xFile.fullPath.isEmpty || isPerformingURL))
}
.navigationTitle("Upload Template")
.scrollContentBackground(.hidden)
}
.fileImporter(
isPresented: $isShowingDocumentPicker,
allowedContentTypes: [.spreadsheet, .commaSeparatedText], allowsMultipleSelection: false) { result in
switch result {
case .success(let file):
xFile.fillXFile(fileURL: file[0], xUser: xUser)
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
}
class HTTPHandler: NSObject, ObservableObject {
@Published var statusMsg = ""
@Published var stage = FileStage.makeTicket
@Published var errorMsg = ""
func asyncCall(request: URLRequest, xFile: XFile) async -> Int {
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse
else {
DispatchQueue.main.async { [self] in
statusMsg = "Unknown Error 1"
}
return 1
}
if httpResponse.statusCode == 200 {
if xFile.jsonAndXFile(data: data) {
if xFile.hData.status == "SUCCESS" &&
xFile.hData.returnvalue == "SSFILEUPLOADED" {
xFile.fileStage = .converting
DispatchQueue.main.async { [self] in statusMsg = "Converting"}
} else if xFile.hData.status == "SUCCESS" &&
xFile.hData.returnvalue == "SSCONVERTING" {
DispatchQueue.main.async { [self] in statusMsg = String(format: "Converting %@%%", xFile.hData.message)}
} else if xFile.hData.status == "SUCCESS" &&
xFile.hData.returnvalue == "SSSTAGE1" {
DispatchQueue.main.async { [self] in statusMsg = String(format: "Converting %@%%", xFile.hData.message)}
}
} else {
DispatchQueue.main.async { [self] in
statusMsg = "JSON err"
}
return 4
}
} else {
DispatchQueue.main.async { [self] in
statusMsg = String(format: "URL error: %d", httpResponse.statusCode)}
return 3
}
} catch {
DispatchQueue.main.async { [self] in
statusMsg = "async error: 2"}
return 2
}
return 0
}
}