From f60c70f32ac2e97430faec23e675b1c9d51fa1ef Mon Sep 17 00:00:00 2001 From: Schwifty Date: Sun, 22 Mar 2026 21:45:10 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20QR=20scanner=20crash=20=E2=80=94=20m?= =?UTF-8?q?issing=20NSCameraUsageDescription=20in=20Info.plist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The app crashed immediately when tapping QR scan because the Info.plist was missing the required NSCameraUsageDescription key. iOS kills the app with EXC_BAD_INSTRUCTION when camera access is requested without it. Also fixes: - Flash toggle could SIGABRT if lockForConfiguration failed (try? + unconditional unlock) - Camera setup now logs errors instead of silently failing Co-Authored-By: Claude Opus 4.6 (1M context) --- PayfritBeacon/Info.plist | 2 ++ PayfritBeacon/Views/QRScannerView.swift | 24 +++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/PayfritBeacon/Info.plist b/PayfritBeacon/Info.plist index 7ea74e1..43eff72 100644 --- a/PayfritBeacon/Info.plist +++ b/PayfritBeacon/Info.plist @@ -22,6 +22,8 @@ $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS + NSCameraUsageDescription + Payfrit Beacon needs camera access to scan QR codes on beacon labels for provisioning. NSBluetoothAlwaysUsageDescription Payfrit Beacon needs Bluetooth to detect and configure nearby beacons. NSBluetoothPeripheralUsageDescription diff --git a/PayfritBeacon/Views/QRScannerView.swift b/PayfritBeacon/Views/QRScannerView.swift index 4b9e830..d3a9d0f 100644 --- a/PayfritBeacon/Views/QRScannerView.swift +++ b/PayfritBeacon/Views/QRScannerView.swift @@ -241,17 +241,31 @@ final class CameraPreviewUIView: UIView { func setFlash(_ on: Bool) { guard let device = AVCaptureDevice.default(for: .video), device.hasTorch else { return } - try? device.lockForConfiguration() - device.torchMode = on ? .on : .off - device.unlockForConfiguration() + do { + try device.lockForConfiguration() + device.torchMode = on ? .on : .off + device.unlockForConfiguration() + } catch { + NSLog("[QRScanner] Failed to set torch: \(error.localizedDescription)") + } } private func setupCamera() { let session = AVCaptureSession() session.sessionPreset = .high - guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back), - let input = try? AVCaptureDeviceInput(device: device) else { return } + guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { + NSLog("[QRScanner] ERROR: No back camera available") + return + } + + let input: AVCaptureDeviceInput + do { + input = try AVCaptureDeviceInput(device: device) + } catch { + NSLog("[QRScanner] ERROR: Failed to create camera input: \(error.localizedDescription)") + return + } if session.canAddInput(input) { session.addInput(input) -- 2.43.0 From 81d4cad0306019ced43b7eaaa197c7aa810aa2c7 Mon Sep 17 00:00:00 2001 From: Schwifty Date: Sun, 22 Mar 2026 21:47:45 +0000 Subject: [PATCH 2/2] fix: back button bounces user right back into selected business MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When tapping Back from ScanView, BusinessListView remounts and .task fires loadBusinesses() which sees AppPrefs.lastBusinessId still set — immediately re-navigating into the same business. Fix: clear lastBusinessId on back, and add skipAutoNav flag to also prevent single-business auto-select from firing when user explicitly navigated back. Co-Authored-By: Claude Opus 4.6 (1M context) --- PayfritBeacon/App/AppState.swift | 5 +++++ PayfritBeacon/Views/BusinessListView.swift | 24 +++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/PayfritBeacon/App/AppState.swift b/PayfritBeacon/App/AppState.swift index 72db257..c88a246 100644 --- a/PayfritBeacon/App/AppState.swift +++ b/PayfritBeacon/App/AppState.swift @@ -14,6 +14,9 @@ final class AppState: ObservableObject { @Published var token: String? @Published var userId: String? + /// When true, skip auto-navigation in BusinessListView (user explicitly went back) + var skipAutoNav = false + init() { // Restore saved session if let saved = SecureStorage.loadSession() { @@ -36,6 +39,8 @@ final class AppState: ObservableObject { } func backToBusinessList() { + AppPrefs.lastBusinessId = nil + skipAutoNav = true currentScreen = .businessList } diff --git a/PayfritBeacon/Views/BusinessListView.swift b/PayfritBeacon/Views/BusinessListView.swift index 74efd03..21658c9 100644 --- a/PayfritBeacon/Views/BusinessListView.swift +++ b/PayfritBeacon/Views/BusinessListView.swift @@ -94,18 +94,22 @@ struct BusinessListView: View { let list = try await APIClient.shared.listBusinesses(token: token) businesses = list - // Auto-navigate if only one business (like Android) - if list.count == 1, let only = list.first { - appState.selectBusiness(only) - return - } + // Skip auto-navigation if user explicitly tapped Back + if !appState.skipAutoNav { + // Auto-navigate if only one business (like Android) + if list.count == 1, let only = list.first { + appState.selectBusiness(only) + return + } - // Auto-navigate to last used business - if let lastId = AppPrefs.lastBusinessId, - let last = list.first(where: { $0.id == lastId }) { - appState.selectBusiness(last) - return + // Auto-navigate to last used business + if let lastId = AppPrefs.lastBusinessId, + let last = list.first(where: { $0.id == lastId }) { + appState.selectBusiness(last) + return + } } + appState.skipAutoNav = false } catch let e as APIError where e.errorDescription == APIError.unauthorized.errorDescription { appState.logout() } catch { -- 2.43.0