From e9b30a80b6a289a5c6201d1d7f1cdbe68cc7f824 Mon Sep 17 00:00:00 2001 From: Maximilian Fischer Date: Wed, 29 Apr 2026 22:35:18 +0200 Subject: [PATCH 1/2] fix: no app crash when building qr code --- .../Views/Projects/ShareProjectQRCode.swift | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/PayForMe/Views/Projects/ShareProjectQRCode.swift b/PayForMe/Views/Projects/ShareProjectQRCode.swift index 95c3d1d..dcc6251 100644 --- a/PayForMe/Views/Projects/ShareProjectQRCode.swift +++ b/PayForMe/Views/Projects/ShareProjectQRCode.swift @@ -5,26 +5,50 @@ // Created by Max Tharr on 03.10.20. // -import AVFoundation -import CarBode import SwiftUI +import CoreImage.CIFilterBuiltins +import UIKit struct ShareProjectQRCode: View { let project: Project - @State var dataString = "" + private static let context = CIContext() + + private var dataString: String { + let server = project.url.relativeString + .replacingOccurrences(of: "https://", with: "") + return "cospend://\(server)/\(project.token)/\(project.password)" + } + + private var qrCodeImage: UIImage { + generateQRCode(from: dataString) + } var body: some View { VStack { Text(dataString).font(.caption) - CarBode.CBBarcodeView(data: $dataString, barcodeType: .constant(.qrCode), orientation: .constant(.up), onGenerated: nil) - .aspectRatio(contentMode: .fit) + + Image(uiImage: qrCodeImage) + .interpolation(.none) + .resizable() + .scaledToFit() } .padding() - .onAppear { - let server = project.url.relativeString.replacingOccurrences(of: "https://", with: "") - dataString = "cospend://\(server)/\(project.token.lowercased())/\(project.password)" + } + + private func generateQRCode(from string: String) -> UIImage { + let filter = CIFilter.qrCodeGenerator() + filter.message = Data(string.utf8) + + guard let outputImage = filter.outputImage else { + return UIImage(systemName: "xmark.circle") ?? UIImage() } + + guard let cgImage = Self.context.createCGImage(outputImage, from: outputImage.extent) else { + return UIImage(systemName: "xmark.circle") ?? UIImage() + } + + return UIImage(cgImage: cgImage) } } From 1bcfaaf3e104c4198bbc58e3e780cf3154567823 Mon Sep 17 00:00:00 2001 From: Maximilian Fischer Date: Thu, 30 Apr 2026 17:35:13 +0200 Subject: [PATCH 2/2] refactor: move qr code generation to viewModel --- PayForMe.xcodeproj/project.pbxproj | 4 ++ .../Views/Projects/ShareProjectQRCode.swift | 44 +++++-------------- .../ShareProjectQRCodeViewModel.swift | 43 ++++++++++++++++++ 3 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 PayForMe/Views/Projects/ShareProjectQRCodeViewModel.swift diff --git a/PayForMe.xcodeproj/project.pbxproj b/PayForMe.xcodeproj/project.pbxproj index 16ef506..333c6e2 100644 --- a/PayForMe.xcodeproj/project.pbxproj +++ b/PayForMe.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 48CC8F7423F5C16D00DCE3D8 /* AddBillView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48CC8F7323F5C16D00DCE3D8 /* AddBillView.swift */; }; 48CF709B240FF82000770B97 /* AddMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48CF709A240FF82000770B97 /* AddMemberView.swift */; }; 48E7F75B2403FD6B000CE4E6 /* FancyLoadingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E7F75A2403FD6B000CE4E6 /* FancyLoadingButton.swift */; }; + 62A13D412FA3AB760036EE34 /* ShareProjectQRCodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A13D402FA3AB700036EE34 /* ShareProjectQRCodeViewModel.swift */; }; 650059FC23DDE1C300D1D599 /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650059FB23DDE1C300D1D599 /* Person.swift */; }; 65005A0223DDFB1A00D1D599 /* WhoPaidView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65005A0123DDFB1A00D1D599 /* WhoPaidView.swift */; }; 65005A0423DE0C5C00D1D599 /* BillList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65005A0323DE0C5C00D1D599 /* BillList.swift */; }; @@ -88,6 +89,7 @@ 48CC8F7323F5C16D00DCE3D8 /* AddBillView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBillView.swift; sourceTree = ""; }; 48CF709A240FF82000770B97 /* AddMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddMemberView.swift; sourceTree = ""; }; 48E7F75A2403FD6B000CE4E6 /* FancyLoadingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyLoadingButton.swift; sourceTree = ""; }; + 62A13D402FA3AB700036EE34 /* ShareProjectQRCodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareProjectQRCodeViewModel.swift; sourceTree = ""; }; 650059FB23DDE1C300D1D599 /* Person.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = ""; }; 65005A0123DDFB1A00D1D599 /* WhoPaidView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhoPaidView.swift; sourceTree = ""; }; 65005A0323DE0C5C00D1D599 /* BillList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BillList.swift; sourceTree = ""; }; @@ -332,6 +334,7 @@ 489995D923F6EC6F008B7E38 /* OnboardingView.swift */, 6523FC56255818F800BCD843 /* Manual */, 654754F12528BB7600A82EB6 /* ShareProjectQRCode.swift */, + 62A13D402FA3AB700036EE34 /* ShareProjectQRCodeViewModel.swift */, 6523FC502558153600BCD843 /* AddPasswordView.swift */, ); path = Projects; @@ -571,6 +574,7 @@ 65662A2423D83ED100303207 /* StorageService.swift in Sources */, 655DD2AE2993DACB00E06675 /* ProjectListEntry.swift in Sources */, 656E8CB823D9FDA300B3ED10 /* BillListViewModel.swift in Sources */, + 62A13D412FA3AB760036EE34 /* ShareProjectQRCodeViewModel.swift in Sources */, 65C9E2D023E1B96B00814B37 /* BalanceViewModel.swift in Sources */, 65662A0723D7320000303207 /* ContentView.swift in Sources */, 65005A0223DDFB1A00D1D599 /* WhoPaidView.swift in Sources */, diff --git a/PayForMe/Views/Projects/ShareProjectQRCode.swift b/PayForMe/Views/Projects/ShareProjectQRCode.swift index dcc6251..6397e2a 100644 --- a/PayForMe/Views/Projects/ShareProjectQRCode.swift +++ b/PayForMe/Views/Projects/ShareProjectQRCode.swift @@ -6,50 +6,28 @@ // import SwiftUI -import CoreImage.CIFilterBuiltins -import UIKit struct ShareProjectQRCode: View { - let project: Project - private static let context = CIContext() + @StateObject private var viewModel: ShareProjectQRCodeViewModel - private var dataString: String { - let server = project.url.relativeString - .replacingOccurrences(of: "https://", with: "") - return "cospend://\(server)/\(project.token)/\(project.password)" - } - - private var qrCodeImage: UIImage { - generateQRCode(from: dataString) + init(project: Project) { + _viewModel = StateObject(wrappedValue: ShareProjectQRCodeViewModel(project: project)) } var body: some View { VStack { - Text(dataString).font(.caption) - - Image(uiImage: qrCodeImage) - .interpolation(.none) - .resizable() - .scaledToFit() + Text(viewModel.dataString).font(.caption) + + if let image = viewModel.qrCodeImage { + Image(uiImage: image) + .interpolation(.none) + .resizable() + .scaledToFit() + } } .padding() } - - private func generateQRCode(from string: String) -> UIImage { - let filter = CIFilter.qrCodeGenerator() - filter.message = Data(string.utf8) - - guard let outputImage = filter.outputImage else { - return UIImage(systemName: "xmark.circle") ?? UIImage() - } - - guard let cgImage = Self.context.createCGImage(outputImage, from: outputImage.extent) else { - return UIImage(systemName: "xmark.circle") ?? UIImage() - } - - return UIImage(cgImage: cgImage) - } } struct ShareProjectQRCode_Previews: PreviewProvider { diff --git a/PayForMe/Views/Projects/ShareProjectQRCodeViewModel.swift b/PayForMe/Views/Projects/ShareProjectQRCodeViewModel.swift new file mode 100644 index 0000000..1753e1b --- /dev/null +++ b/PayForMe/Views/Projects/ShareProjectQRCodeViewModel.swift @@ -0,0 +1,43 @@ +// +// ShareProjectQRCodeViewModel.swift +// PayForMe +// +// Created by Maximilian Fischer on 30.04.26. +// Copyright © 2026 Mayflower GmbH. All rights reserved. +// + +import SwiftUI +import UIKit +import CoreImage.CIFilterBuiltins + +final class ShareProjectQRCodeViewModel: ObservableObject { + + @Published var qrCodeImage: UIImage? + @Published var dataString: String = "" + + private let context = CIContext() + private let project: Project + + init(project: Project) { + self.project = project + generate() + } + + private func generate() { + let server = project.url.relativeString + .replacingOccurrences(of: "https://", with: "") + self.dataString = "cospend://\(server)/\(project.token)/\(project.password)" + self.qrCodeImage = generateQRCode(from: dataString) + } + + private func generateQRCode(from string: String) -> UIImage? { + let filter = CIFilter.qrCodeGenerator() + filter.message = Data(string.utf8) + + guard let outputImage = filter.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { + return UIImage(systemName: "xmark.circle") + } + + return UIImage(cgImage: cgImage) + } +}