SwiftUI API for captureTextFromCamera ?

Hi, the video was great and on point, but it only featured UIKit apis.

3 years into the SwiftUI transition, I wonder if this is a UIKit only feature or can we also use it if we chose SwiftUI to build our apps ?

Thanks

  • Hey. I am asking me the same. Did you get an answer to this question somewhere else?

Add a Comment

Replies

Hello,

To create a button for that purpose, you need a class that adopts the protocols UIResponder and UIKeyInput. The required variable hasText and method deleteBackward are not used so you can just ignore them.

class ScanTextResponder: UIResponder, UIKeyInput {
	init(title: Binding<String>) {
		_title = title
	}

	@Binding var title: String

	var canCaptureTextFromCamera: Bool {
		canPerformAction(#selector(captureTextFromCamera(_:)), withSender: self)
	}

	var hasText = false

	func insertText(_ text: String) {
		title = text
	}

	func deleteBackward() {}
}

Because the feature does not work on older devices, you should add a condition like canCaptureTextFromCamera, to not show the button on these devices. Then you just need to use the responder class in a view like this:

struct ScanTextButton: View {
	init(title: Binding<String>) {
		self.responder = ScanTextResponder(title: title)
	}

	private let responder: ScanTextResponder

	var body: some View {
		if responder.canCaptureTextFromCamera {
			Button {
				responder.captureTextFromCamera(nil)

				// Allows dismissal of the camera after multiple presses
				let backup = responder.title
				responder.title = ""
				responder.title = backup
			} label: {
				Label("Scan Text", systemImage: "text.viewfinder")
			}
		}
	}
}

The last three lines in the button action are not necessary, but I have found that otherwise you can't dismiss the camera view after pressing the button multiple times while the camera is already visible. I haven't found a better way to do that.

Alternatively, there is also the possibility to integrate a UIViewRepresentable. The advantage of that is, that the button label and icon are preconfigured which includes localisation. The disadvantage is that you have to use UIKit if you want to style the button.

struct UIScanTextButton: UIViewRepresentable {
	let coordinator: ScanTextResponder

	func makeCoordinator() -> ScanTextResponder {
		coordinator
	}

	func makeUIView(context: Context) -> UIButton {
		UIButton(primaryAction: .captureTextFromCamera(responder: context.coordinator, identifier: nil))
	}

	func updateUIView(_ uiView: UIViewType, context: Context) {}
}

You can use that in your SwiftUI view like this:

UIScanTextButton(coordinator: responder)
			.fixedSize()

The fixedSize() modifier ensures that the view does not cover the whole screen.

I was struggling with this myself and hope that we get a better way to do this in the future. Let me know if you have any questions.

Funny that this is the only posting I have found on how to do this. It does work as expected but if after a scan and the text is inserted, clicking the Button again does nothing and camera does not show up. You have to actually come off the text field or click within it and then click the button to get the camera to appear again. Any ideas why that is?