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), which creates HTTPHandler, 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.status to keep the user informed of progress.
While everything works without error, if I do not use DispatchQueue.main.async for every change to the Published strings in HTTPHandler, then I get the purple 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.
Is this the way it has to be, or have I missed some step that would not require me to use DispatchQueue being used on so many lines?
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
}
}