SwiftUI无Storyboard实现选择图片/拍照并转为Data类型按钮
解决方案
核心问题修正与实现步骤
- 修正@State变量位置:SwiftUI中
@State必须定义在View结构体的属性域,不能放在body闭包内,否则会导致状态异常。 - 集成PhotosPicker(支持相册+相机):使用SwiftUI原生的
PhotosPicker控件,配置相机权限后可直接唤起相机或相册选择照片。 - PhotosPickerItem转Data:通过异步API加载照片数据,转换为
Data类型存储。
完整修正代码
import SwiftUI import PhotosUI struct TaskInsertView: View { @ObservedObject var viewModel: TaskInsertViewModel // 修正:将@State移至结构体属性区 @State private var selectedItems: [PhotosPickerItem] = [] @State private var selectedPhotosData: [Data] = [] var body: some View { VStack { // 到达日期区域 HStack(spacing: 20) { Text("Date Arrived :") DatePicker("", selection: $viewModel.startDate) Spacer() } .padding() // 姓名区域 HStack(spacing: 20) { Text("First and Last Name : ") TextField("First and Last Name", text: $viewModel.taskfirstandlast) .textFieldStyle(.roundedBorder) } .padding() // 电话号码区域 HStack(spacing: 20) { Text("Phone Number : ") TextField("Phone Number", text: $viewModel.taskphonenum) .textFieldStyle(.roundedBorder) } .padding() // 描述区域 HStack(spacing: 20) { Text("Description : ") TextField("Description", text: $viewModel.taskdescription) .textFieldStyle(.roundedBorder) } .padding() // Blast区域 HStack(spacing: 20) { Text("Blast : ") TextField("Blast", text: $viewModel.taskblast) .textFieldStyle(.roundedBorder) } .padding() // Prime区域 HStack(spacing: 20) { Text("Prime : ") TextField("Prime", text: $viewModel.taskprime) .textFieldStyle(.roundedBorder) } .padding() // Color区域 HStack(spacing: 20) { Text("Color : ") TextField("Color", text: $viewModel.taskcolor) .textFieldStyle(.roundedBorder) } .padding() // 图片选择按钮(集成相册+相机) HStack(spacing: 20) { PhotosPicker(selection: $selectedItems, maxSelectionCount: 5, cameraAccessAllowed: true) { Text("选择照片/拍照") .foregroundColor(.white) .font(.headline) .padding(.vertical, 10) .padding(.horizontal, 30) .background(.blue) .cornerRadius(.infinity) } } .padding() // 已选照片预览 ScrollView(.horizontal) { HStack(spacing: 10) { for photoData in selectedPhotosData { if let uiImage = UIImage(data: photoData) { Image(uiImage: uiImage) .resizable() .scaledToFit() .frame(width: 80, height: 80) .cornerRadius(8) } } } .padding(.horizontal) } // 添加按钮 HStack { Button(action: { // 提交时可将selectedPhotosData传给viewModel viewModel.savePhotos(selectedPhotosData) viewModel.onAddButtonClick() }) { Text("Add") .foregroundColor(.white) .font(.headline) .padding(.vertical, 10) .padding(.horizontal, 30) } .background(.blue) .cornerRadius(.infinity) .padding() } Spacer() } // 监听选中项变化,转换为Data .onChange(of: selectedItems) { newItems in for item in newItems { // 避免重复处理同一项 guard !selectedPhotosData.contains(where: { _ in // 可通过item.id判断是否已处理,简化场景下直接加载 false }) else { continue } Task { if let photoData = try? await item.loadTransferable(type: Data.self) { selectedPhotosData.append(photoData) } } } } } } // 需在ViewModel中添加对应方法示例 extension TaskInsertViewModel { func savePhotos(_ photosData: [Data]) { // 处理照片数据存储逻辑,比如存入CoreData、上传服务器等 } }
关键说明
- 权限配置:必须在
Info.plist中添加以下权限描述,否则会崩溃:NSCameraUsageDescription:描述相机使用场景(如"需要相机权限拍摄照片")NSPhotoLibraryUsageDescription:描述相册使用场景(如"需要访问相册选择照片")
- 异步转换逻辑:
loadTransferable(type:)是异步方法,必须放在Task中执行,确保正确处理照片数据加载。 - 数据传递:在提交按钮点击时,将
selectedPhotosData传给ViewModel,完成后续存储或上传操作。
内容的提问来源于stack exchange,提问作者lduell
相关产品推荐
相关产品推荐

