import SwiftUI import PhotosUI /// Artikelfoto ansehen, per Kamera aufnehmen oder aus der Galerie wählen und /// hochladen. Wird als Abschnitt in die Produkt-Detailansicht eingebettet. struct ProductPhotoSection: View { let productId: Int @State private var imageData: Data? @State private var loaded = false @State private var busy = false @State private var error: String? @State private var showCamera = false @State private var pickerItem: PhotosPickerItem? var body: some View { Section("Foto") { if let imageData, let ui = UIImage(data: imageData) { Image(uiImage: ui) .resizable().scaledToFit() .frame(maxWidth: .infinity) .frame(maxHeight: 240) .clipShape(RoundedRectangle(cornerRadius: 8)) } else if loaded { Text("Noch kein Foto.").foregroundStyle(.secondary) } Button { showCamera = true } label: { Label("Foto machen", systemImage: "camera") } PhotosPicker(selection: $pickerItem, matching: .images) { Label("Aus Galerie", systemImage: "photo.on.rectangle") } if imageData != nil { Button(role: .destructive) { Task { await removePhoto() } } label: { Label("Foto entfernen", systemImage: "trash") } } if busy { ProgressView() } if let error { Text(error).foregroundStyle(.red).font(.callout) } } .fullScreenCover(isPresented: $showCamera) { CameraPicker(isPresented: $showCamera) { image in Task { await upload(image) } } .ignoresSafeArea() } .onChange(of: pickerItem) { item in guard let item else { return } Task { if let data = try? await item.loadTransferable(type: Data.self), let ui = UIImage(data: data) { await upload(ui) } pickerItem = nil } } .task { await load() } } private func load() async { imageData = try? await APIClient.shared.productImage(id: productId) loaded = true } private func upload(_ image: UIImage) async { guard let data = Self.prepared(image) else { return } busy = true defer { busy = false } error = nil do { _ = try await APIClient.shared.uploadProductImage( id: productId, data: data, contentType: "image/jpeg") imageData = data } catch { self.error = error.localizedDescription } } private func removePhoto() async { busy = true defer { busy = false } error = nil do { try await APIClient.shared.deleteProductImage(id: productId) imageData = nil } catch { self.error = error.localizedDescription } } /// Foto verkleinern und als JPEG unter die Server-Grenze (2 MB) drücken. private static func prepared(_ image: UIImage, maxDim: CGFloat = 1600) -> Data? { let size = image.size let scale = min(1, maxDim / max(size.width, size.height)) let target = CGSize(width: size.width * scale, height: size.height * scale) let scaled = UIGraphicsImageRenderer(size: target).image { _ in image.draw(in: CGRect(origin: .zero, size: target)) } var quality: CGFloat = 0.8 var data = scaled.jpegData(compressionQuality: quality) while let d = data, d.count > 1_800_000, quality > 0.3 { quality -= 0.15 data = scaled.jpegData(compressionQuality: quality) } return data } } /// Kamera über UIImagePickerController – SwiftUI hat keine eigene Kamera-Ansicht. /// Ohne Kamera (Simulator) fällt es auf die Fotoauswahl zurück. struct CameraPicker: UIViewControllerRepresentable { @Binding var isPresented: Bool var onImage: (UIImage) -> Void func makeUIViewController(context: Context) -> UIImagePickerController { let picker = UIImagePickerController() picker.sourceType = UIImagePickerController.isSourceTypeAvailable(.camera) ? .camera : .photoLibrary picker.delegate = context.coordinator return picker } func updateUIViewController(_ picker: UIImagePickerController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } // Schließen deterministisch über das Binding statt über die Umgebung – // das behebt das Flackern (Kamera geht direkt wieder zu), wenn die Ansicht // während der Präsentation neu ausgewertet wird. final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { let parent: CameraPicker init(_ parent: CameraPicker) { self.parent = parent } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { if let image = info[.originalImage] as? UIImage { parent.onImage(image) } parent.isPresented = false } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { parent.isPresented = false } } }