I’ve looked at a lot of the answers on here and tried different solutions such as just making my view model @MainActor and not just the property but I am still getting this warning message
“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.”
and I can’t figure out why. I’ve even gone to the extent of just putting @MainActors on all my functions in TestViewViewModel and still the same result.
I have two views that each have their own view models. One view is in charge of presenting the data that’s tied to a view mode that handles all the logic when it comes to “uploading” content while the other is to trigger the action which is the footer view that communicates back to the main view to upload the transactions sending a signal to the other view model.
The main view is simply this:
struct TestSwiftUIView: View {
@ObservedObject var viewModel: TestViewViewModel = TestViewViewModel()
var body: some View {
VStack {
headerButtonView()
List(viewModel.countData) { model in
Text("\(model.number)")
}
}
.onAppear {
viewModel.loadData()
}
}
@ViewBuilder
func headerButtonView() -> some View {
let vm = UploadButtonViewModel(pendingTransactions: viewModel.countData.count,
showProgress: viewModel.showProgressBar,
uploadCount: viewModel.uploadCount)
UploadButtonView(viewModel: vm)
.onReceive(vm.$uploadState) { state in
switch state {
case .ready:
break
case .upload:
viewModel.beingUploadTransactions = true
}
}
}
}
This is what TestViewModel looks like:
class TestViewViewModel: ObservableObject {
@MainActor @Published private(set) var countData: [NumberModel] = []
@Published var beingUploadTransactions: Bool = false
@Published var uploadTransactionCount: Float = 0.0
@Published var showProgressBar: Bool = false
@Published var uploadCount: Float = 0
private var cancellables: Set<AnyCancellable> = []
init() {
setupBindings()
}
@MainActor
func loadData() {
for x in 0..<20 {
countData.append(NumberModel(number: x))
}
}
private func setupBindings() {
$beingUploadTransactions
.sink { [weak self] value in
guard let self = self else { return }
if value {
Task { @MainActor in
try await self.uploadTransactions()
}
}
}
.store(in: &cancellables)
}
private func uploadTransactions() async throws {
showProgressBar = true
Task {
for x in 0...20 {
uploadCount = Float(x)
try await Task.sleep(for: .milliseconds(50))
}
}
}
}
The upload action gets triggered when I tap on the UploadButtonViewModel which sends back the state into the TestSwiftUIView through the on receive:
class UploadButtonViewModel: ObservableObject {
enum UploadState {
case ready
case upload
}
@Published private(set) var showProgressBar: Bool
@Published private(set) var uploadCount: Float
@Published var uploadState: UploadState = .ready
private(set) var pendingTransactions: Int
init(pendingTransactions: Int, showProgress: Bool = false, uploadCount: Float = 0) {
self.pendingTransactions = pendingTransactions
self.showProgressBar = showProgress
self.uploadCount = uploadCount
}
}
struct UploadButtonView: View {
@ObservedObject private var viewModel: UploadButtonViewModel
init(viewModel: UploadButtonViewModel) {
self.viewModel = viewModel
}
var body: some View {
VStack {
if viewModel.showProgressBar {
Text("\(viewModel.uploadCount)")
progressBar
} else {
Button {
viewModel.uploadState = .upload
} label: {
Text("Upload Count")
}
}
}
}
var progressBar: some View {
GeometryReader { geo in
ZStack(alignment: .leading) {
Rectangle()
.frame(width: geo.size.width, height: geo.size.height)
.foregroundColor(.gray)
Rectangle()
.frame(width: min(CGFloat(viewModel.uploadCount / Float(viewModel.pendingTransactions)) * geo.size.width, geo.size.width), height: geo.size.height)
.foregroundColor(Color(UIColor.systemTeal))
.animation(.linear, value: viewModel.showProgressBar)
}
}
}
}
some crappy limitation that’s I am on is I have to use iOS 13 as finally my work is adopting swiftUI but still a very old legacy code base.




