ios – why do I keep Publishing changes from background threads is not allowed


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.

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img