iOS: Scanner – Pinch-Zoom und schlaueres Blitzlicht
- Zwei-Finger-Pinch zoomt im Kamerabild naeher an einen Code heran (digital, auf Faktor 6 begrenzt). - Blitz geht nach dem ersten erfolgreichen Scan wieder aus. Per Langdruck auf den Blitz-Knopf bleibt er dauerhaft an (gesperrt, gelb); Tippen schaltet aus und hebt die Sperre auf. Gilt fuer alle Scan-Bildschirme. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,9 @@ struct ScannerView: UIViewControllerRepresentable {
|
||||
var onCode: (String) -> Void
|
||||
@Binding var isPaused: Bool
|
||||
@Binding var torchOn: Bool
|
||||
/// Per Langdruck gesperrter Blitz bleibt auch nach einem Scan an. Standard:
|
||||
/// aus nach dem ersten Treffer.
|
||||
@Binding var torchLocked: Bool
|
||||
|
||||
func makeCoordinator() -> Coordinator { Coordinator(onCode: onCode) }
|
||||
|
||||
@@ -18,12 +21,16 @@ struct ScannerView: UIViewControllerRepresentable {
|
||||
|
||||
func updateUIViewController(_ controller: ScannerViewController, context: Context) {
|
||||
context.coordinator.isPaused = isPaused
|
||||
context.coordinator.torchLocked = torchLocked
|
||||
context.coordinator.torchBinding = $torchOn
|
||||
controller.setTorch(on: torchOn)
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, ScannerViewControllerDelegate {
|
||||
let onCode: (String) -> Void
|
||||
var isPaused: Bool = false
|
||||
var torchLocked: Bool = false
|
||||
var torchBinding: Binding<Bool>?
|
||||
private var lastCode: String?
|
||||
private var lastTime: Date = .distantPast
|
||||
|
||||
@@ -37,21 +44,42 @@ struct ScannerView: UIViewControllerRepresentable {
|
||||
lastTime = Date()
|
||||
AudioServicesPlaySystemSound(1057)
|
||||
onCode(code)
|
||||
// Blitz nach dem Scan ausschalten – ausser er ist per Langdruck gesperrt.
|
||||
if !torchLocked, torchBinding?.wrappedValue == true {
|
||||
DispatchQueue.main.async { [weak self] in self?.torchBinding?.wrappedValue = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Licht-Schalter fuer die Scan-Bildschirme.
|
||||
///
|
||||
/// Tippen: an/aus – angeschaltet geht der Blitz nach dem naechsten Scan wieder
|
||||
/// aus. Langdruck: dauerhaft an (gesperrt, gelb), bleibt auch nach Scans an;
|
||||
/// erneutes Tippen schaltet aus und hebt die Sperre auf.
|
||||
struct TorchButton: View {
|
||||
@Binding var isOn: Bool
|
||||
@Binding var locked: Bool
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
locked = false
|
||||
isOn.toggle()
|
||||
} label: {
|
||||
Image(systemName: isOn ? "bolt.fill" : "bolt.slash.fill")
|
||||
.foregroundStyle(locked ? Color.yellow : Color.accentColor)
|
||||
}
|
||||
.accessibilityLabel(isOn ? "Licht ausschalten" : "Licht einschalten")
|
||||
.simultaneousGesture(
|
||||
LongPressGesture(minimumDuration: 0.4).onEnded { _ in
|
||||
isOn = true
|
||||
locked = true
|
||||
}
|
||||
)
|
||||
.accessibilityLabel(
|
||||
isOn
|
||||
? (locked ? "Licht dauerhaft an, zum Ausschalten tippen" : "Licht aus")
|
||||
: "Licht ein (bleibt bis zum nächsten Scan)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +93,8 @@ final class ScannerViewController: UIViewController, AVCaptureMetadataOutputObje
|
||||
private let session = AVCaptureSession()
|
||||
private var preview: AVCaptureVideoPreviewLayer?
|
||||
private var device: AVCaptureDevice?
|
||||
/// Zoomfaktor beim Beginn einer Pinch-Geste (Ausgangspunkt fuers Skalieren).
|
||||
private var zoomBasis: CGFloat = 1.0
|
||||
|
||||
/// Auch krummere Symbologien mitnehmen: ITF-14 kommt auf Umkartons vor,
|
||||
/// DataMatrix auf kleinen Aufklebern.
|
||||
@@ -77,6 +107,7 @@ final class ScannerViewController: UIViewController, AVCaptureMetadataOutputObje
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .black
|
||||
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(focusOnTap)))
|
||||
view.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(zoomPinch)))
|
||||
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
|
||||
guard granted else { return }
|
||||
DispatchQueue.main.async { self?.configure() }
|
||||
@@ -164,6 +195,20 @@ final class ScannerViewController: UIViewController, AVCaptureMetadataOutputObje
|
||||
device.unlockForConfiguration()
|
||||
}
|
||||
|
||||
/// Zwei-Finger-Pinch zoomt naeher an einen Code heran (rein digital, auf ein
|
||||
/// sinnvolles Maximum begrenzt – zu viel Zoom macht den Code unscharf).
|
||||
@objc private func zoomPinch(_ gesture: UIPinchGestureRecognizer) {
|
||||
guard let device else { return }
|
||||
if gesture.state == .began {
|
||||
zoomBasis = device.videoZoomFactor
|
||||
}
|
||||
let maxZoom = min(device.activeFormat.videoMaxZoomFactor, 6.0)
|
||||
let neu = max(1.0, min(zoomBasis * gesture.scale, maxZoom))
|
||||
guard (try? device.lockForConfiguration()) != nil else { return }
|
||||
device.videoZoomFactor = neu
|
||||
device.unlockForConfiguration()
|
||||
}
|
||||
|
||||
/// Kurzer gelber Fokus-Rahmen an der getippten Stelle als Rueckmeldung.
|
||||
private func showFocusIndicator(at point: CGPoint) {
|
||||
let seite: CGFloat = 72
|
||||
|
||||
Reference in New Issue
Block a user