iOS: Foto schon beim Anlegen eines Produkts hinterlegen
Anlege-Formular bekommt eine Foto-Sektion (Kamera/Galerie) mit Vorschau. Das Bild wird nach dem Anlegen hochgeladen (dann gibt es eine Produkt-ID). Die JPEG-Aufbereitung ist jetzt eine freie Funktion (productImageJPEG), von der Detail- und Anlege-Ansicht gemeinsam genutzt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -196,7 +196,7 @@ struct ProductPhotoSection: View {
|
||||
}
|
||||
|
||||
private func upload(_ image: UIImage) async {
|
||||
guard let data = Self.prepared(image) else { return }
|
||||
guard let data = productImageJPEG(image) else { return }
|
||||
busy = true
|
||||
defer { busy = false }
|
||||
error = nil
|
||||
@@ -223,8 +223,11 @@ struct ProductPhotoSection: View {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Foto verkleinern und als JPEG unter die Server-Grenze (2 MB) drücken.
|
||||
private static func prepared(_ image: UIImage, maxDim: CGFloat = 1600) -> Data? {
|
||||
/// Frei (nicht an eine View gebunden), damit auch das Anlege-Formular es nutzt.
|
||||
func productImageJPEG(_ 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)
|
||||
@@ -239,7 +242,6 @@ struct ProductPhotoSection: View {
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
/// Kamera über UIImagePickerController – SwiftUI hat keine eigene Kamera-Ansicht.
|
||||
/// Ohne Kamera (Simulator) fällt es auf die Fotoauswahl zurück.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import PhotosUI
|
||||
|
||||
/// Artikel per Namenssuche auswählen (Alternative zum Scannen).
|
||||
struct ProductSearchView: View {
|
||||
@@ -84,6 +85,10 @@ struct ProductFormView: View {
|
||||
@State private var newGroupShown = false
|
||||
@State private var busy = false
|
||||
@State private var error: String?
|
||||
// Foto schon beim Anlegen: erst nach dem Anlegen hochladen (ID nötig).
|
||||
@State private var pendingImage: UIImage?
|
||||
@State private var showCamera = false
|
||||
@State private var pickerItem: PhotosPickerItem?
|
||||
|
||||
private let baseUnits = [("piece", "Stück"), ("gram", "Gramm"), ("milliliter", "Milliliter")]
|
||||
// Beschriftungen kommen sonst aus DisplaySettings.baseUnitLabel.
|
||||
@@ -174,6 +179,25 @@ struct ProductFormView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Foto direkt beim Anlegen – z.B. wenn Open Food/Products Facts keins
|
||||
// liefert. Wird nach dem Anlegen hochgeladen (dann gibt es eine ID).
|
||||
Section("Foto") {
|
||||
if let pendingImage {
|
||||
Image(uiImage: pendingImage)
|
||||
.resizable().scaledToFit()
|
||||
.frame(maxWidth: .infinity).frame(maxHeight: 200)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
Button { showCamera = true } label: { Label("Foto machen", systemImage: "camera") }
|
||||
PhotosPicker(selection: $pickerItem, matching: .images) {
|
||||
Label("Aus Galerie", systemImage: "photo.on.rectangle")
|
||||
}
|
||||
if pendingImage != nil {
|
||||
Button(role: .destructive) { pendingImage = nil } label: {
|
||||
Label("Foto entfernen", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
if let error {
|
||||
Section { Text(error).foregroundStyle(.red).font(.callout) }
|
||||
}
|
||||
@@ -184,6 +208,20 @@ struct ProductFormView: View {
|
||||
}
|
||||
.navigationTitle("Neuer Artikel")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.fullScreenCover(isPresented: $showCamera) {
|
||||
CameraPicker(isPresented: $showCamera) { image in pendingImage = 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) {
|
||||
pendingImage = ui
|
||||
}
|
||||
pickerItem = nil
|
||||
}
|
||||
}
|
||||
.onAppear(perform: prefill)
|
||||
.task {
|
||||
groups = (try? await APIClient.shared.groups()) ?? []
|
||||
@@ -242,6 +280,12 @@ struct ProductFormView: View {
|
||||
bulk: modus == "object" ? (objMode == "bulk") : nil
|
||||
)
|
||||
)
|
||||
// Vorgemerktes Foto hochladen – ein Fehler darf das Anlegen nicht
|
||||
// zunichtemachen, der Artikel existiert bereits.
|
||||
if let pendingImage, let data = productImageJPEG(pendingImage) {
|
||||
_ = try? await APIClient.shared.uploadProductImage(
|
||||
id: product.id, data: data, contentType: "image/jpeg")
|
||||
}
|
||||
onCreated(product)
|
||||
dismiss()
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user