Why do I get a "Publishing changes from within view updates is not allowed" when moving my @Bindings to @Published in an @ObservableObject?

Am going through a SwiftUI course, so the code is not my own.

When I migrated my @Bindings into @Published items in an @ObservableObject I started getting the following error:

> Publishing changes from within view updates is not allowed, this will cause undefined behavior.

The warning occurs in the ScannerView which is integrated with the main view, BarcodeScannerView. It occurs when an error occurs, and scannerView.alertItem is set to a value.

However, it does not occur when I am setting the value of scannerView.scannedCode, and as far as I can tell, they both come from the sample place, and are the same actions.

There are tons of posts like mine, but I have yet to find an answer. Any thoughts or comments would be very appreciated.

BarcodeScannerView

import SwiftUI

struct BarcodeScannerView: View {
	
	@StateObject var viewModel = BarcodeScannerViewModel()
	
	var body: some View {
		NavigationStack {
			VStack {
				ScannerView(scannedCode: $viewModel.scannedCode, typeScanned: $viewModel.typeScanned, alertItem: $viewModel.alertItem)
					.frame(maxWidth: .infinity, maxHeight: 300)
				Spacer().frame(height: 60)
				BarcodeView(statusText: viewModel.typeScanned)
				TextView(statusText: viewModel.statusText, statusTextColor: viewModel.statusTextColor)
			}
			.navigationTitle("Barcode Scanner")
			.alert(item: $viewModel.alertItem) { alertItem in
				Alert(title: Text(alertItem.title), message: Text(alertItem.message), dismissButton: alertItem.dismissButton)
			}
		}
	}
}

BarcodeScannerViewModel

import SwiftUI

final class BarcodeScannerViewModel: ObservableObject {

	@Published  var scannedCode = ""
	@Published  var typeScanned = "Scanned Barcode"
	@Published  var alertItem: AlertItem?
	
	var statusText: String {
		return scannedCode.isEmpty ? "Not Yet scanned" : scannedCode
	}

	var statusTextColor: Color {
		scannedCode.isEmpty ? .red : .green
	}
}

ScannerView

import SwiftUI

struct ScannerView: UIViewControllerRepresentable {

	typealias UIViewControllerType = ScannerVC
	
	@Binding var scannedCode : String
	@Binding var typeScanned : String
	@Binding var alertItem:	AlertItem?

	func makeCoordinator() -> Coordinator {
		Coordinator(scannerView: self)
	}
	

	func makeUIViewController(context: Context) -> ScannerVC {
		ScannerVC(scannerDelegate: context.coordinator)
	}
	
	func updateUIViewController(_ uiViewController: ScannerVC, context: Context) {
		
	}
	
	final class Coordinator: NSObject, ScannerVCDelegate {

		private let scannerView: ScannerView
		
		init(scannerView: ScannerView) {
			self.scannerView = scannerView
		}
		
		func didFind(barcode: String, typeScanned: String) {
			scannerView.scannedCode = barcode
			scannerView.typeScanned = typeScanned
			print (barcode)
		}
		
		func didSurface(error: CameraError) {
			switch error {
				case .invalidDeviceinput:
					scannerView.alertItem = AlertContext.invalidDeviceInput
				case .invalidScannedValue:
					scannerView.alertItem = AlertContext.invalidScannedValue
				case .invalidPreviewLayer:
					scannerView.alertItem = AlertContext.invalidPreviewLayer
				case .invalidStringObject:
					scannerView.alertItem = AlertContext.invalidStringObject
			}
		}
	}
}
  • Where exactly do you get the error ?

  • I get it at case .invalidDeviceinput: However, I added a case for a successful scan, and when that case pops up, I do not get the warning.

Add a Comment

Accepted Reply

So I went to ChatGPT to see if it could help me solve this problem. Encased the code that causes the alert to popup with:

DispatchQueue.main.async { [self] in
...
}

and now I no longer get this warning. Is it that simple? Is CHatGPT that scary?

  • I assume because the other published vars aren't affected, that it has to do with the fact that the NavigationView is wrapped in an .alert(item: ) might have something to do with this warning and supposed solution?

    Won't mark this solved, will wait for some more input from other developers, as I am too green.

Add a Comment

Replies

Should the BarcodeScannerViewModel be the ScannerVCDelegate instead of the view? The delegate then updates the published values, view getting the updates through that.

So I went to ChatGPT to see if it could help me solve this problem. Encased the code that causes the alert to popup with:

DispatchQueue.main.async { [self] in
...
}

and now I no longer get this warning. Is it that simple? Is CHatGPT that scary?

  • I assume because the other published vars aren't affected, that it has to do with the fact that the NavigationView is wrapped in an .alert(item: ) might have something to do with this warning and supposed solution?

    Won't mark this solved, will wait for some more input from other developers, as I am too green.

Add a Comment