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.