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

SwiftUI无Storyboard实现选择图片/拍照并转为Data类型按钮

解决方案

核心问题修正与实现步骤

  1. 修正@State变量位置:SwiftUI中@State必须定义在View结构体的属性域,不能放在body闭包内,否则会导致状态异常。
  2. 集成PhotosPicker(支持相册+相机):使用SwiftUI原生的PhotosPicker控件,配置相机权限后可直接唤起相机或相册选择照片。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 06:47:02