You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中相机获取图片并保存至SwiftData的问题求助

问题:SwiftUI相机选择照片后无法保存至SwiftData

我在SwiftUI中实现相机视图功能,已成功显示相机界面并选择照片,但无法将图片保存至SwiftData。核心需求是获取照片后将UIImage转换为可存储格式,存入review.foodImage字段。


编辑餐食视图(Edit Meal View)

import SwiftUI
import PhotosUI
import SwiftData
import UIKit

struct EditMealView: View {
    @Binding var isPresentingNewMealView: Bool
    @Environment(\.modelContext) private var modelContext
    @State var mealImage: Image? = nil
    @State private var selectedItem: PhotosPickerItem? = nil
    @Bindable var review: Review
    @State private var imageData: Data?
    @State var selection: TextSelection? = nil
    @State private var locationService = LocationService(completer: .init())
    @State private var search: String = ""
    @State private var isPresentingLocationView = false
    
    @State private var sourceType: UIImagePickerController.SourceType = .photoLibrary
    @State private var selectedImage: UIImage?
    @State private var isImagePickerDisplay = false
    
    
    let foodTypeList: [String] = ["Fast Food", "Pub Food", "Vegetarian", "Central African", "Happy Hour", "Dog Friendly", "Family Friendly", "East African", "North African", "Southern Africa", "West African", "Latin American", "Native American", "Canadian", "Carribean", "Mexican", "American", "Southern American", "Fusion", "Central American", "South American", "Chinese", "Japanese", "Korean", "Indian", "Middle Eastern", "Thai", "Turkish", "Vietnamese", "Italian", "French", "German", "Spanish", "Greek", "Romanian", "Russian", "Eastern European", "Scandinavian", "British", "Dutch", "Swedish", "Norwegian", "Icelandic", "Irish", "Polynesian", "Australian", "New Zealand", "Other"].sorted()
    
    var body: some View {
        NavigationStack {
            ZStack {
                LinearGradient(gradient: Gradient(colors: [Color(red: 0.9725490196078431, green: 0.9607843137254902, blue: 0.8627450980392157, opacity: 1), Color(red: 0.9137254901960784, green: 0.9254901960784314, blue: 0.8509803921568627, opacity: 1), Color(red: 0.9882352941176471, green: 0.7529411764705882, blue: 0.13333333333333333, opacity: 1), Color(red: 0.9450980392156862, green: 0.47058823529411764, blue: 0.08627450980392157, opacity: 1)]), startPoint: .top, endPoint: .bottom).ignoresSafeArea()
                
                    .navigationTitle("Add Meal")
                
                VStack{
                    Form {
                        
                        TextField("Menu Item", text: $review.foodName)
                            .listRowBackground(Color.clear)
                            .autocorrectionDisabled()
                        
                        HStack {
                            Button(action: {isPresentingLocationView = true}
                            ) {
                                Text("Location")
                                Image(systemName: "map")
                            }
                            
                            .sheet(isPresented: $isPresentingLocationView) {
                                AddressView(isPresentingLocationView: $isPresentingLocationView, review: review)
                            }
                            VStack {
                                Text("\(review.restaurantName)")
                                    .bold()
                                    .listRowBackground(Color.clear)
                                    .autocorrectionDisabled()
                                Text("\(review.location)")
                                    .listRowBackground(Color.clear)
                                    .autocorrectionDisabled()
                            }
                            .padding()
                            
                        }
                        .listRowBackground(Color.clear)
                        Picker(selection: $review.foodType, label: Text("Food Category")) {
                            ForEach(0..<foodTypeList.count, id: \.self) {
                                Text(self.foodTypeList[$0])
                                    .tag(self.foodTypeList[$0])
                            }
                            
                        }
                        .listRowBackground(Color.clear)
                        
                        HStack{
                            Spacer()
                            RatingView(rating: $review.rating)
                                .multilineTextAlignment(TextAlignment.center)
                            Spacer()
                        }
                        .buttonStyle(.plain)
                        .listRowBackground(Color.clear)
                        
                        if let imageData = review.foodImage, let uiImage = UIImage(data: imageData) {
                            Image(uiImage: uiImage)
                                .resizable()
                                .scaledToFit()
                        }
                        
                        HStack{

                            Spacer()
                            Button("", systemImage: "camera.fill")
                            {
                                self.sourceType = .camera
                                self.isImagePickerDisplay.toggle()
                                print("camera")
                            }.padding()
                               
                            Button("", systemImage: "photo.fill.on.rectangle.fill") {
                                self.sourceType = .photoLibrary
                                self.isImagePickerDisplay.toggle()
                                print( "photo library")
                            }.padding()
                            Spacer()
                        }
                        .sheet(isPresented: self.$isImagePickerDisplay) {
                        ImagePickerView(selectedImage: self.$selectedImage, sourceType: self.sourceType)
                            
                        }
                        

                        .listRowBackground(Color.clear)
                        .padding()
                        .buttonStyle(BorderlessButtonStyle())
                        
                        Text("Notes")
                            .font(.headline)
                            .listRowBackground(Color.clear)
                        
                        TextEditor(text: $review.reviewNotes, selection: $selection)
                            .background(Color.white.opacity(0.40))
                            .listRowBackground(Color.clear)
                    }
                    .listRowBackground(Color.clear)
                    .padding()
                    Spacer()
                }
            }
            
        
        .scrollDismissesKeyboard(.immediately)
        .scrollContentBackground(.hidden)
        .toolbar {
            ToolbarItem(placement: .cancellationAction) {
                Button("Dismiss") {
                    isPresentingNewMealView = false
                }
            }
            ToolbarItem(placement: .confirmationAction) {
                Button("Save") {
                    let newReview = review
                    review.lastModified = Date.now
                    modelContext.insert(newReview)
                    isPresentingNewMealView = false
                    
                }
            }
        }
        }
        
    }
    
    
    func loadPhoto() {
        Task { @MainActor in
            review.foodImage = selectedImage?.pngData()
        }
    }
}

图片选择器视图(Image picker view)

import SwiftUI
import UIKit

struct ImagePickerView: UIViewControllerRepresentable {
    @Binding var selectedImage: UIImage?
    @Environment(\.presentationMode) var isPresented
    
    var sourceType: UIImagePickerController.SourceType
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.sourceType = self.sourceType
        return imagePicker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {

    }
    
    // Connecting the Coordinator class with this struct
    func makeCoordinator() -> Coordinator {
        return Coordinator(picker: self)
    }
    
    
}

class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
    
    var picker: ImagePickerView
    
    init(picker: ImagePickerView) {
        self.picker = picker
    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let selectedImage = info[.originalImage] as? UIImage else { return }
        self.picker.selectedImage = selectedImage
        self.picker.isPresented.wrappedValue.dismiss()
    }
}

问题分析与修复方案

1. 核心问题点

  • 未给UIImagePickerController设置代理,导致图片选择完成后不会触发回调
  • 定义了loadPhoto()转换图片方法,但从未调用
  • Save按钮逻辑冗余,无需创建newReview实例

2. 具体修复步骤

步骤1:修复ImagePicker的代理设置

在ImagePickerView的makeUIViewController方法中添加代理绑定:

func makeUIViewController(context: Context) -> UIImagePickerController {
    let imagePicker = UIImagePickerController()
    imagePicker.sourceType = self.sourceType
    imagePicker.delegate = context.coordinator // 添加这行
    return imagePicker
}

步骤2:添加图片选择完成后的回调

修改ImagePickerView,增加图片选择完成的回调闭包:

struct ImagePickerView: UIViewControllerRepresentable {
    @Binding var selectedImage: UIImage?
    @Environment(\.dismiss) var dismiss // 替换旧的presentationMode
    var sourceType: UIImagePickerController.SourceType
    var onImageSelected: (UIImage) -> Void // 新增回调
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.sourceType = self.sourceType
        imagePicker.delegate = context.coordinator
        return imagePicker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(picker: self)
    }
    
    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        var picker: ImagePickerView
        
        init(picker: ImagePickerView) {
            self.picker = picker
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            guard let selectedImage = info[.originalImage] as? UIImage else { return }
            self.picker.selectedImage = selectedImage
            self.picker.onImageSelected(selectedImage) // 触发回调
            self.picker.dismiss()
        }
    }
}

然后在EditMealView的sheet中传入回调,触发图片转换:

.sheet(isPresented: self.$isImagePickerDisplay) {
    ImagePickerView(selectedImage: self.$selectedImage, sourceType: self.sourceType) { _ in
        self.loadPhoto()
    }
}

步骤3:优化Save按钮逻辑

Button("Save") {
    review.lastModified = Date.now
    modelContext.insert(review)
    isPresentingNewMealView = false
}

步骤4:确保Review模型字段正确

确认Review模型中foodImage为Data?类型,符合SwiftData存储要求:

@Model
class Review {
    var foodName: String
    var restaurantName: String
    var location: String
    var foodType: String
    var rating: Int
    var reviewNotes: String
    var lastModified: Date
    var foodImage: Data? // 必须是Data?类型
    
    init(foodName: String = "", restaurantName: String = "", location: String = "", foodType: String = "", rating: Int = 0, reviewNotes: String = "", lastModified: Date = Date(), foodImage: Data? = nil) {
        self.foodName = foodName
        self.restaurantName = restaurantName
        self.location = location
        self.foodType = foodType
        self.rating = rating
        self.reviewNotes = reviewNotes
        self.lastModified = lastModified
        self.foodImage = foodImage
    }
}

内容的提问来源于stack exchange,提问作者user29801994

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 15:57:01