网站开发公司商业计划书,网上营销策划方案,网站运营的内容,企业网站seo名称存储图片
在前面的示例中#xff0c;我们在屏幕上展示了图片#xff0c;但也可以将其存储到文件或数据库中。另外有时使用相机将照片存储到设备的相册薄里会很有用#xff0c;这样可供其它应用访问。UIKit框架提供了如下两个保存图片和视频的函数。
UIImageWriteToSavedPh…存储图片
在前面的示例中我们在屏幕上展示了图片但也可以将其存储到文件或数据库中。另外有时使用相机将照片存储到设备的相册薄里会很有用这样可供其它应用访问。UIKit框架提供了如下两个保存图片和视频的函数。
UIImageWriteToSavedPhotosAlbum(UIImage, Any?, Selector?, UnsafeMutableRawPointer?)该函数将第一个参数所指定的图像添加到相册。第二个参数是在处理结束后包含所要执行方法的对象的指针第三个参数是表示该方法的选择器最后一个参数是传递给该方法数据的指针。UISaveVideoAtPathToSavedPhotosAlbum(String, Any?, Selector?, UnsafeMutableRawPointer?)该函数将第一个参数所指定路径的视频添加到相册。第二个参数是在处理结束后包含所要执行方法的对象的指针第三个参数是表示该方法的选择器最后一个参数是传递给该方法数据的指针。 注意将照片或视频存储到设备中必须要有用户的授权。没错通过应用配置的Info面板可以实现。对于本例必须添加Privacy - Camera Usage Description选项设置请求授权时向用户展示的信息。 这些是在Objective-C中定义的老方法因此用到了一些在SwiftUI应用中不常见的参数。但如果只是要保存图片我们可以只指定第一个参数将其它的定义为nil。例如可以在前面的应用界面的上方添加一个按钮打开带两个按钮的警告视图一个按钮用于取消操作另一个用于将当前图片保存到相册。点击按钮保存图片时我们可以调用UIImageWriteToSavedPhotosAlbum传入picture属性的指针图片就会被保存。
示例18-8在保存图片时显示警告视图
struct ContentView: View {State private var path NavigationPath()State private var picture: UIImage?State private var showAlert: Bool falsevar body: some View {NavigationStack(path: $path) {VStack {HStack {Button(Share Picture) {showAlert true}.disabled(picture nil ? true : false)Spacer()NavigationLink(Get Picture, value: Open Picker)}.navigationDestination(for: String.self, destination: { _ inImagePicker(path: $path, picture: $picture)}).alert(Save Picture, isPresented: $showAlert, actions: {Button(Cancel, role: .cancel, action: {showAlert false})Button(YES, role: .none, action: {if let picture {UIImageWriteToSavedPhotosAlbum(picture, nil, nil, nil)}})}, message: { Text(Do you want to store the picture in the Photo Library?) })Image(uiImage: picture ?? UIImage(named: nopicture)!).resizable().scaledToFit().frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).clipped()Spacer()}.padding()}.statusBarHidden()}
}
流程和之前相同。图片选择控制器让用户可以拍照然后调用代理方法来处理。图片赋值给picture属性来在屏幕上进行显示但此时我们多了一个按钮可以将图片保存到照片库中。
✍️跟我一起做使用示例18-8中的代码更新ContentView.swift文件。在应用的info面板中添加Privacy - Photo Library Additions Usage Description选项来获取照片库的访问权限。别忘了还需要和之前一样配置Privacy - Camera Usage Description来获取相机的权限。在设备上运行应用、拍照。应该会在屏幕上看到照版。点击Share Picture按钮会弹出一个警告框要求获取权限。点击YES。这时相片会保存到照片库中。
分享链接
另一种与其它应用分享信息的方式是分享弹窗。这个弹窗由系统提供通过图标可打开希望共享内容的应用同时带有拷贝和打印信息的选项。SwiftUI提供了如下打开弹窗的视图。
ShareLink(String, item: Item, subject: Text?, message: Text?, preview: SharePreview)这一初始化方法创建一个按钮可打开弹窗选择希望共享数据的应用。第一个参数是按钮的标签。item参数是希望共享的值必须符合Transferable协议。subject参数是内容的标题。message参数是内容的描述。preview参数是提供内容展示的结构体。
如果希望共享图片必须提供预览。为此SwiftUI内置了SharePreview结构体。
SharePreview(String, image: Image)这一初始化方法创建一个分享内容的展示。第一个参数是内容的描述image参数是在视觉上表现内容的Image视图。
分享链接经常用于分享文本但也可以分享其它内容只要内容符合Transferable协议即可。例如我们可以分享拍摄的照片。
示例18-9对其它应用分享图像
struct ContentView: View {State private var path NavigationPath()State private var picture: UIImage?var body: some View {NavigationStack(path: $path) {VStack {HStack {if let picture picture {let photo Image(uiImage: picture)ShareLink(Share Picture, item: photo, preview: SharePreview(Photo, image: photo))}Spacer()NavigationLink(Get Picture, value: Open Picker)}.navigationDestination(for: String.self, destination: { _ inImagePicker(path: $path, picture: $picture)})Image(uiImage: picture ?? UIImage(named: nopicture)!).resizable().scaledToFill().frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).clipped()Spacer()}.padding()}.statusBarHidden()}
}
ShareLink视图使用左侧带有SF图标的预定义标签创建按钮。本例中我们将其放在左上角但仅在有图片可共享时才显示如果用户已使用相机拍摄照片。按下按钮后系统会打开一个小弹窗其中包含可分享信息的应用图标在弹窗中向上滚动时会看到拷贝和打印数据等其它操作选项。例如假设我们安装了Facebook就可以像下图这样通过图片发帖。 图18-6分享弹窗
✍️跟我一起做使用示例18-9中的代码更新ContentView视图。在设备上运行应用。点击Get Picture按钮拍照。然后点击Share Picture按钮。会在屏幕底部弹出分享弹窗。选择分享图片的应用。
自定义相机
UIImagePickerController控制器通过AV Foundation框架中定义的类构建。该框架提供了处理媒体资源和控制输入设备所需的代码。因此可以使用框架中的类直接构建自己的控制器以及自定义处理流程和界面。
创建访问相机从输入设备获取信息的自定义控制器要求多系统的协同我们需要配置相机和麦克风的输入、处理通过这些输入接收到数据、对用户提供预览并生成图片、实时图片、视频或音频形式的输出。图18-7所有相关的元素。 图18-7 捕获媒体资源的系统
构建之初我们需要确定输入设备。AV Foundation框架为此定义了AVCaptureDevice类。该类的实例可表示任意输入设备包括相机和麦克风。下面是该类中包含的访问和管理设备的一方法。
default(for: AVMediaType)这一类型方法返回一个表示参数指定的默认捕获媒体资源设备的AVCaptureDevice对象。for参数是一个AVMediaType类型的结构体包含定义媒体类型的属性。用于操作相机和麦克风的属性为video和audio。requestAccess(for: AVMediaType)向用户请求访问设备权限的异步类型方法。for参数是一个AVMediaType类型的结构体包含定义媒体类型的属性。用于操作相机和麦克风的属性为video和audio。authorizationStatus(for: AVMediaType)该类型方法返回决定使用设备权限状态的值。for参数是一个AVMediaType类型的结构体包含定义媒体类型的属性。用于操作相机和麦克风的属性为video和audio。该方法返回AVAuthorizationStatus类型的枚举值有notDetermined、restricted、denied和authorized。
AVCaptureDevice类的实例表示一个捕获的设备如相机或麦克风。该类包含用于配置和管理设备的属性和方法。以下为其中最常用的以及我们在例子中要用到的。
isSubjectAreaChangeMonitoringEnabled该属性设置或返回一个布尔值决定设备是否监测修改的区域如光照和朝向。formats该属性返回一个Format对象的数组表示设备支持的格式。activeFormat该属性返回一个Format对象表示设备当前使用的格式。lockForConfiguration()该方法请求对配置设备的独占访问。unlockForConfiguration()该方法释放所配置的设备。
要将捕获的设备定义为输入设备我们必须创建控制商品和连接对象。框架为此定义了AVCaptureDeviceInput类。类中包含如下创建设备输入对象的初始化方法。
AVCaptureDeviceInput(device: AVCaptureDevice)该初始化方法创建由device参数指定的设备输入。
除输入外我们还需要输出来捕获和处理从其它设备接收到设备。框架定义了基类AVCaptureOutput的子类 来描述输出。有多个可用的子类 比如处理视频帧的AVCaptureVideoDataOutput和获取音频数据的AVCaptureAudioDataOutput但最有用的还是AVCapturePhotoOutput类用于捕获单个视频帧拍照。这个类包含很多配置输出的属性和方法。下面是设置最大图片尺寸的属性和捕获照片的方法。
maxPhotoDimensions此属性设置或返回待捕获图片的大小。这一个CMVideoDimensions类型的结构体包含属性width和height。capturePhoto(with: AVCapturePhotoSettings, delegate: AVCapturePhotoCaptureDelegate)该方法通过with参数指定的设置初始化照片抓取。delegate参数是一个对象指针对象实现了AVCapturePhotoCaptureDelegate协议中接收输出生成数据的方法。
AVCapturePhotoOutput类与符合AVCapturePhotoCaptureDelegate协议的委托一起返回一个静止图片其中定义有如下方法。
photoOutput(AVCapturePhotoOutput, didFinishProcessingPhoto: AVCapturePhoto, error: Error?)该方法在捕获图片后对委托进行调用。didFinishProcessingPhoto参数是一个容器包含有关图片的信息error参数用于报告错误。
为控制输入到输出的数据流框架定义了AVCaptureSession类。通过该类的实例我们可以通过调用如下方法控制输入、输出并决定处理何时开始和结束。
addInput(AVCaptureInput)此方法向捕获会话添加输入。参数表示希望添加的输入设备。addOutput(AVCaptureOutput)此方法向捕获会话添加输出。参数表示我们希望从捕获会话生成的输出。startRunning()此方法开启捕获会话。stopRunning()此方法停止捕获会话。
框架还定义了AVCaptureVideoPreviewLayer类向用户展示预览。该类创建一个图层展示输入设备捕获的视频。类中包含如下创建和管理预览图层的初始化方法和属性。
AVCaptureVideoPreviewLayer(session: AVCaptureSession)此初始化方法创建一个连接由session参数定义的捕获会话的AVCaptureVideoPreviewLayer对象.connection此属性返回AVCaptureConnection类型的对象定义捕获会话与预览图层间的连接。
输入、输出和预览图层通过AVCaptureConnection类的对象与捕捉会话进行连接。该类管理连接信息有端口、数据和朝向。以下是用于设置预览层朝向的属性和方法。
videoRotationAngle该属性返回表示连接应用于预览的旋转角度的CGFloat值角度为0.0, 90.0, 180.0, 270.0。isVideoRotationAngleSupported(CGFloat)该方法返回一个布尔值表示是否支持由参数所指定的旋转角度。
旋转角度由旋转coordinator决定。框架在AVCaptureDevice类中定义了RotationCoordinator来进行创建。该类中包含如下初始化方法。
AVCaptureDevice.RotationCoordinator(device: AVCaptureDevice, previewLayer: CALayer?)该谢谢学姐为设备创建一个旋转coordinator以及由参数指定的预览层。
在RotationCoordinator类中包含如下两个属性可读取获取当前旋转角度。
videoRotationAngleForHorizonLevelPreview该属性返回需应用于预览层的旋转角度与设备朝向进行匹配。videoRotationAngleForHorizonLevelCapture该属性返回需应用于相机抓取图像的旋转角度与设备朝向进行匹配。
本例所创建的界面与前面的相近。需要一个按钮打开视图允许用户用相机拍照以及一个在屏幕上显示照片的Image视图。 图18-8自定义相机界面
启动相机以及获取用户所拍相片的处理与界面相独立但如果希望用户看到来自相机的图片我们需要创建一个预览层。图层是视图在屏幕上展示图像的方式。视图定义区域并提供功能但图像由CALayer类创建的图层进行展示。UIView类创建的每个包含可用于展示视频的图层但图层必须转换为AVCaptureVideoPreviewLayer。为此我们需要创建一个UIView的子类 重载类型属性layerClass将该视频的图层转换为预览图层然后创建一个UIViewRepresentable结构体来展示SwiftUI界面中的视图。
示例18-10定义一个UIView的子类展示相机的预览视频
import SwiftUI
import AVFoundationclass CustomPreviewView: UIView {override class var layerClass: AnyClass {return AVCaptureVideoPreviewLayer.self}
}
struct CustomPreview: UIViewRepresentable {let view CustomPreviewView()func makeUIView(context: Context) - UIView {return view}func updateUIView(_ uiView: UIView, context: Context) {}
}
layerClass属性是系统读取决定图层数据类型的类型属性。本例中我们重载了该属性返回AVCaptureVideoPreviewLayer类的指针这样系统知道我们使用这一视图层来显示视频。representable视图的其它代码和之前一样。本例我们会在model中管理所有相机的逻辑。以下是配置系统所需的基本元素。
示例18-11定义管理相机的属性
import SwiftUI
import Observation
import AVFoundationclass ViewData {var captureDevice: AVCaptureDevice?var captureSession: AVCaptureSession?var stillImage: AVCapturePhotoOutput?var rotationCoordinator: AVCaptureDevice.RotationCoordinator?var previewObservation: NSKeyValueObservation?
}
Observable class ApplicationData: NSObject, AVCapturePhotoCaptureDelegate {var path NavigationPath()var picture: UIImage?ObservationIgnored var cameraView: CustomPreview!ObservationIgnored var viewData: ViewData!override init() {cameraView ç()viewData ViewData()}
}
以上代码是这模型的第一部分我们还要添加一些方法来启用和控制相机但它提供了需要存储系统各个元素指针的属性。因这些属性在多个方法中用到我们将其声明到了单独的类ViewData。在初始化模型时我们创建了此类的实例和表征视图CustomPreview将它们存在于非观测属性中供其它代码访问。
下一步是定义方法获取访问相机的权限。如果使用UIImagePickerController控制器这会自动实现但在自定义控制器中我们需要使用AVCaptureDevice类所提供的方法自己实现。以下是在模型中添加的对应方法。
示例18-12请求使用相机的权限
func getAuthorization() async {let granted await AVCaptureDevice.requestAccess(for: .video)await MainActor.run {if granted {self.prepareCamera()} else {print(Not Authorized)}}}
requestAccess()方法是异步的它等待用户响应返回Bool类型的值报告结果。如果用户授权访问我们执行prepareCamera()方法。这里我们开始构建图18-7中介绍的对象网络。该方法必须获取当前视频拾取设备的指针创建我们抓取静止图像拍照的输入和输出。
示例18-13初始化相机
func prepareCamera() {viewData.captureSession AVCaptureSession()viewData.captureDevice AVCaptureDevice.default(for: AVMediaType.video)if let _ try? viewData.captureDevice?.lockForConfiguration() {viewData.captureDevice?.isSubjectAreaChangeMonitoringEnabled trueviewData.captureDevice?.unlockForConfiguration()}if let device viewData.captureDevice {if let input try? AVCaptureDeviceInput(device: device) {viewData.captureSession?.addInput(input)viewData.stillImage AVCapturePhotoOutput()if viewData.stillImage ! nil {viewData.captureSession?.addOutput(viewData.stillImage!)if let max viewData.captureDevice?.activeFormat.supportedMaxPhotoDimensions.last {viewData.stillImage?.maxPhotoDimensions max}}showCamera()} else {print(Not Authorized)}} else {print(Not Authorized)}}
该方法一开始新建会话并请求对相机的访问。如果default()方法返回值如就将true赋值给isSubjectAreaChangeMonitoringEnabled属性开启对设备朝向变化的监控。
有了会话和设备访问权限后我们就可以定义所需的输入和输出。并没有特别的顺序要求但因为AVCapturePhotoOutput()初始化方法会抛错误我们先使用了它。这个初始化方法创建一个管理捕获设备输入的对象。如果成功使用addInput()将其添加到捕获会话再创建输出。
本例中我们希望使用会话捕获静止图像。因此我们使用AVCapturePhotoOutput类创建输出将其添加到会话然后配置返回允许的最大尺寸的图像。注意最大尺寸由maxPhotoDimensions属性决定但不能对其赋自定义值。我们需要获取相机可生成的可用尺寸列表并使用最大的那个。实现这一任务我们读取activeFormat属性获取相机当前使用格式的Format对象并读取其supportedMaxPhotoDimensions属性。这个属性返回一个CMVideoDimensions结构体数组包含设备所支持的尺寸我们获取最后一个赋值给输出得到尽可能大尺寸的图像。
在读取输入、输出获取捕获会话后prepareCamera()方法还执行showCamera()方法定义预览层并在屏幕上显示来自相机的视频。
示例18-14在屏幕上显示来自相机的视频
func showCamera() {let previewLayer cameraView.view.layer as? AVCaptureVideoPreviewLayerpreviewLayer?.session viewData.captureSessionif let device viewData.captureDevice, let preview previewLayer {viewData.rotationCoordinator AVCaptureDevice.RotationCoordinator(device: device, previewLayer: preview)preview.connection?.videoRotationAngle viewData.rotationCoordinator!.videoRotationAngleForHorizonLevelCaptureviewData.previewObservation viewData.rotationCoordinator!.observe(\.videoRotationAngleForHorizonLevelPreview, changeHandler: { old, value inpreview.connection?.videoRotationAngle self.viewData.rotationCoordinator!.videoRotationAngleForHorizonLevelPreview})}Task(priority: .background) {viewData.captureSession?.startRunning()}}
前面已经提到包含UIView类创建视图的图层由CALayer类型对象定义。这是显示图像和执行动画的基类。但要显示来自相机的视频我们需要将其转换为AVCaptureVideoPreviewLayer对象。在将图层转化为预览层后我们可以创建旋转协调器来设置视频的朝向。协调器检测设备和预览层并将当前旋转角度存储到videoRotationAngleForHorizonLevelPreview属性中因此我们将该属性的值赋给AVCaptureConnection对象的videoRotationAngle属性来设置当前朝向。为保持该值实时更新我们对属性videoRotationAngleForHorizonLevelPreview添加了观察者每当该属性值发生改变时设置视频的朝向参见第14章的键/值观察。准备好预览层和旋转协调器后捕获会话通过startRunning()方法初始化。系统要求该方法在后台线程中执行。
此时视频在屏幕上播放系统可以捕捉图像了。捕捉图像的过程由AVCapturePhotoOutput对象提供的capturePhoto()方法初始化输出的图片类型由AVCapturePhotoSettings对象决定。该类包含多个初始化方法。以下是最常用的那个。
AVCapturePhotoSettings()此初始化方法使用默认格式创建一个AVCapturePhotoSettings对象。
以下是该类中用于配置图像和预览的一些属性。
maxPhotoDimensions该属性设置或返回所捕捉图片的尺寸。这是一个CMVideoDimensions类型的结构体包含属性width和height。previewPhotoFormat该属性设置或返回一个字典包含的键和值决定预览图片的特征。键包括kCVPixelBufferPixelFormatTypeKey (未压缩格式), kCVPixelBufferWidthKey (宽) and kCVPixelBufferHeightKey (高)。flashMode该属性设置或返回捕捉图像时使用的闪光灯模式。这是一个FlashMode类型的枚举值有on、off和auto。
配置图像我们要通过AVCapturePhotoSettings对象定义设置调用AVCapturePhotoOutput对象的capturePhoto()方法并定义接收图像的委托方法。以下是需要向模型添加的拍照方法。
示例18-15拍照
func takePicture() {let settings AVCapturePhotoSettings()if let max viewData.captureDevice?.activeFormat.supportedMaxPhotoDimensions.last {settings.maxPhotoDimensions max}viewData.stillImage?.capturePhoto(with: settings, delegate: self)}
在用户点击按钮拍照时执行takePicture()方法并调用capturePhoto()方法请求捕捉图像的输出对象。捕捉图像后此对象将结果发送给委托对象见示例18-11因此我们可以在模型内实现委托方法。参见下面我们对该方法的实现。
示例18-16处理图像
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {let scene UIApplication.shared.connectedScenes.first as? UIWindowScenelet scale scene?.screen.scale ?? 1let orientationAngle viewData.rotationCoordinator!.videoRotationAngleForHorizonLevelCapturevar imageOrientation: UIImage.Orientation!switch orientationAngle {case 90.0:imageOrientation .rightcase 270.0:imageOrientation .leftcase 0.0:imageOrientation .upcase 180.0:imageOrientation .downdefault:imageOrientation .right}if let imageData photo.cgImageRepresentation() {picture UIImage(cgImage: imageData, scale: scale, orientation: imageOrientation)path NavigationPath()}}
photoOutput(AVCapturePhotoOutput, didFinishProcessingPhoto:)方法接收相机所生成的图片。方法接收的值是AVCapturePhoto类型的对象这是一个带有图片信息的容器。类中包含两个方便获取表示图像的数据的方法。
fileDataRepresentation()该方法返回可用于创建UIImage对象的图像数据形式。cgImageRepresentation()该方法以UIImage对象Core Graphic返回图像。
在本例中我们实现了cgImageRepresentation()方法因为UIImage类定义了一个便捷的初始化方法可通过包含缩放比例和朝向的CGImage创建图像。通过旋转协调器的videoRotationAngleForHorizonLevelCapture属性获取朝向。该属性返回带有旋转角度的CGFloat值我们可将其转换为Orientation值来设置图像的朝向参见第10章中的UIImage。要设置缩放比例我们需要访问屏幕。屏幕由UIScreen类的对象进行管理自动按设备创建并赋值给Scene属性。因此要访问屏幕和缩放比例我们需要读取UIWindowScene对象它通过UIApplication对象的connectedScenes属性控制当前场景。我们在第14章中介绍过这个对象。它由系统创建用于控制应用。该对象由shared类提供的类型属性返回。要访问应用所打开的场景我们读取connectedScenes属性。本例我们为移动设备开发应用因此只需要访问第一个场景。UIWindowScene对象包含screen属性返回表示屏幕的UIScreen对象指针而UIScreen对象包含有返回当前比例的scale属性以及屏幕大小的bounds属性。通过这些值我们创建了UIImage对象并将其赋值给picture属性更新视图及显示图像如下所示。
示例18-17显示图像
struct ContentView: View {Environment(ApplicationData.self) private var appDatavar body: some View {NavigationStack(path: Bindable(appData).path) {VStack {HStack {Spacer()NavigationLink(Take Picture, value: Open Camera)}.navigationDestination(for: String.self, destination: { _ inCustomCameraView()})Image(uiImage: appData.picture ?? UIImage(named: nopicture)!).resizable().scaledToFill().frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).clipped()Spacer()}.padding().navigationBarHidden(true)}.statusBarHidden()}
}
示例18-17中并没有太多新内容只是不再打开包含标准界面的UIImagePickerController我们打开了一个需添加供用户拍照的按钮和自定义控件的视图。以下是对该视图的实现。
示例18-18拍照
import SwiftUIstruct CustomCameraView: View {Environment(ApplicationData.self) private var appDatavar body: some View {ZStack {appData.cameraViewVStack {Spacer()HStack {Button(Cancel) {appData.path NavigationPath()}Spacer()Button(Take Picture) {appData.takePicture()}}.padding().frame(height: 80).background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 0.8))}}.edgesIgnoringSafeArea(.all).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).navigationBarHidden(true).task {await appData.getAuthorization()}.onDisappear {appData.viewData.previewObservation nil}}
}
如图18-8右图所示该视频包含有UIView显示 来自相机的视频以及底部的另一个视图包含两个按钮一个用于取消处理和释放视频另一个用于拍照。该视图出现在屏幕上时我们调用getAuthorization()方法来启动处理。如果用户点击Take Picture按钮我们调用takePicture()方法捕捉图像。处理好图像后委托方法释放该视图并在屏幕上显示图像。注意我们应用了onDisappear()修饰符来删除观察者。这样可以确保在不需要时不再有活跃的观察者。
✍️跟我一起做创建一个多平台项目。下载nopicture.png图片将其添加到资源目录。使用示例18-10的代码创建CustomPreview.swift用示例18-11的代码创建ApplicationData.swift。在模型中添加示例18-12、18-13、18-14、18-15及18-16中的方法。用示例18-17中的代码更新ContentView视图。创建一个SwiftUI文件CustomCameraView.swift代码见示例18-18。别忘了在应用设置的Info面板中添加rivacy - Camera Usage Description选项并将ApplicationData对象注入到应用和预览中参见第7章示例7-4。在设备中运行应用并拍照测试。