SwiftUI调用相机拍照

时间:2024-06-01 18:59:53

在 SwiftUI 中实现拍照功能,需要结合 UIViewControllerRepresentableUIImagePickerController 来实现相机功能。下面是一个详细的示例,展示如何使用 SwiftUI 来实现拍照功能:

1. 创建一个 ImagePicker 组件

首先,创建一个 UIViewControllerRepresentable 结构,用于包装 UIImagePickerController

import SwiftUI
import UIKit

struct ImagePicker: UIViewControllerRepresentable {
    @Binding var selectedImage: UIImage?
    @Environment(\.presentationMode) var presentationMode
    var sourceType: UIImagePickerController.SourceType = .camera

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        let parent: ImagePicker

        init(parent: ImagePicker) {
            self.parent = parent
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let image = info[.originalImage] as? UIImage {
                parent.selectedImage = image
            }

            parent.presentationMode.wrappedValue.dismiss()
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.presentationMode.wrappedValue.dismiss()
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        picker.sourceType = sourceType
        return picker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
}

2. 使用 ImagePicker 组件

接下来,在你的主视图中使用 ImagePicker 组件来实现拍照功能。

import SwiftUI

struct ContentView: View {
    @State private var isImagePickerPresented = false
    @State private var selectedImage: UIImage?

    var body: some View {
        VStack {
            if let selectedImage = selectedImage {
                Image(uiImage: selectedImage)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 300, height: 300)
            } else {
                Text("No Image Selected")
                    .frame(width: 300, height: 300)
                    .background(Color.gray)
            }
            
            Button(action: {
                isImagePickerPresented = true
            }) {
                Text("Take Photo")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            .padding()
        }
        .sheet(isPresented: $isImagePickerPresented) {
            ImagePicker(selectedImage: $selectedImage)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

解释

  1. ImagePicker 组件

    • UIViewControllerRepresentable 协议用来将 UIImagePickerController 引入 SwiftUI。
    • makeUIViewControllerupdateUIViewController 方法创建和更新 UIImagePickerController
    • Coordinator 类作为 UIImagePickerController 的代理,处理图片选择和取消操作。
  2. ContentView

    • 使用 @State 属性包装变量 isImagePickerPresented 来控制 ImagePicker 的显示。
    • 使用 @State 属性包装变量 selectedImage 来存储选取的图片。
    • 当点击 “Take Photo” 按钮时,显示 ImagePicker
    • sheet 修饰符用于在 isImagePickerPresentedtrue 时呈现 ImagePicker

通过这种方式,你可以在 SwiftUI 应用中实现拍照功能。请注意,拍照功能只能在真实设备上使用,因为模拟器不支持摄像头。

在 iOS 应用中访问相机需要在 Info.plist 文件中添加 NSCameraUsageDescription 键,以告知用户为什么需要访问相机。否则,应用在尝试访问相机时会崩溃。

添加 NSCameraUsageDescriptionInfo.plist

  1. 打开你的 Xcode 项目。

  2. 在项目导航中,找到并点击你的 Info.plist 文件。

  3. Info.plist 中,添加一个新的键值对:

    • 键:NSCameraUsageDescription
    • 值:解释你的应用需要使用相机的原因,比如 “This app requires access to the camera to take photos.”

示例:

<key>NSCameraUsageDescription</key>
<string>This app requires access to the camera to take photos.</string>

如果你使用 Xcode 的图形化界面,可以按以下步骤操作:

  1. 打开 Info.plist 文件。
  2. 点击右键选择 “Add Row”。
  3. 在新行的键列中输入 NSCameraUsageDescription
  4. 在值列中输入对用户的说明,比如 “This app requires access to the camera to take photos.”

更新后的示例代码

完成上述步骤后,你可以重新运行之前的代码:

import SwiftUI
import UIKit

struct ImagePicker: UIViewControllerRepresentable {
    @Binding var selectedImage: UIImage?
    @Environment(\.presentationMode) var presentationMode
    var sourceType: UIImagePickerController.SourceType = .camera

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        let parent: ImagePicker

        init(parent: ImagePicker) {
            self.parent = parent
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let image = info[.originalImage] as? UIImage {
                parent.selectedImage = image
            }
            parent.presentationMode.wrappedValue.dismiss()
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.presentationMode.wrappedValue.dismiss()
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        picker.sourceType = sourceType
        return picker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
}

struct ContentView: View {
    @State private var isImagePickerPresented = false
    @State private var selectedImage: UIImage?

    var body: some View {
        VStack {
            if let selectedImage = selectedImage {
                Image(uiImage: selectedImage)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 300, height: 300)
            } else {
                Text("No Image Selected")
                    .frame(width: 300, height: 300)
                    .background(Color.gray)
            }
            
            Button(action: {
                isImagePickerPresented = true
            }) {
                Text("Take Photo")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            .padding()
        }
        .sheet(isPresented: $isImagePickerPresented) {
            ImagePicker(selectedImage: $selectedImage)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

通过添加 NSCameraUsageDescription,应用在请求访问相机时会向用户显示一条提示,解释为什么需要访问相机,从而避免因未声明权限而导致的崩溃。