使用AVFoundation完成照片拍摄存储相册, 开启关闭闪光灯, 切换摄像头

在开启这个旅程之前, 请记住, AVFoundation是一个复杂的工具. 在很多情况下, 我我们使用苹果默认的API(比如:UIImagePickerController)就足够了.

在您阅读之前, 请确保您确实使用过AVFoundation

由于swift 版本不同, 你可能在XCode上面编写时候部分语法有差异,不过相信广大小伙伴们都是可以简单应对的.??????

回话, 设备, 输入和输出

拍摄照片和视频的核心是CaptureSession(捕获回话). 在苹果的介绍中捕获回话是"an object that manages capture activity and coordinates the flow of data from input devices to capture outputs." 一种管理捕获活动并协调来自输入设备的数据流以捕获输出的对象。另外, 捕获设备用于实际的iOS设备上可用的物理音频和视频捕获设备(即摄像头和麦克风), 如果要使用AVFoundation需要我们捕获设备(即获取摄像头和麦克风对象).

然后通过捕获输入, 提供给回话对象, 在将结果保存在输出中. 通俗说就是同涉嫌头(输入对象) 将拍摄的结果提供给会对对象, 在由回话对象将结果显示屏幕或者其他输出对象中

然后在将结果保存在捕获的对象.

示例图:

示例项目

如果你想通过自己的手来探索框架, 请您在实例项目上工作, 但是为了让我们专注于讨论AVFoundation框架, 我附带了一个入门项目, 在继续之前, 请点击下载并快速查看.

示例项目是当前项目的基础, 里面包含了:

1. Assets.xcassets 包含我们项目所需要的必要的图像文件. 声明:这些图片来自于互联网. 如果有涉及侵权, 请联系我, 我将第一时间予以删除

2.里面有一个Storyboard文件. 此视图控制器将用于处理我们的应用程序内所有图片和视频的拍摄

3.一个ViewController控制器,用于处理交互

如下图

好的, 我们开始吧!

在此我们将设计一个CameraController, 负责完成拍摄照片和视频录制有关的工作

请在我们的项目中声明一个类 CameraController 类, 继承NSObject

1 import AVFoundation
2
3 class CameraController: NSObject { }

照片拍摄

首先,我们将使用后置摄像头实现照片捕捉功能。这将是我们的基本功能,我们将添加切换相机,使用闪光灯,并添加到我们的照片捕捉功能录制视频的能力。由于配置和启动捕获会话是一个相对密集的过程,我们将解耦它init并创建一个叫做prepare的函数,准备捕获会话以供使用,并在完成时调用完成处理程序。

1
2 func prepare(completionHandler: @escaping (Error?) -> Void) { }

这个函数将处理新捕获会话的创建和配置。请记住,设置捕获会话由4个步骤组成:

  1. 创建一个捕获会话。
  2. 获取和配置必要的捕获设备。
  3. 使用捕获设备创建输入。
  4. 配置照片输出对象以处理拍摄的图像。

我们将使用Swift的嵌套函数以可管理的方式封装我们的代码。首先声明4个空函数prepare,然后调用它们:

 1 func prepare(completionHandler: @escaping (Error?) -> Void) {
 2     func createCaptureSession() { }
 3     func configureCaptureDevices() throws { }
 4     func configureDeviceInputs() throws { }
 5     func configurePhotoOutput() throws { }
 6
 7     DispatchQueue(label: "prepare").async {
 8         do {
 9             createCaptureSession()
10             try configureCaptureDevices()
11             try configureDeviceInputs()
12             try configurePhotoOutput()
13         }
14
15         catch {
16             DispatchQueue.main.async {
17                 completionHandler(error)
18             }
19
20             return
21         }
22
23         DispatchQueue.main.async {
24             completionHandler(nil)
25         }
26     }
27 }

在上面的代码中,我们创建了样板函数来执行准备AVCaptureSession照片捕捉的4个关键步骤。我们还设置了一个异步执行的块,调用这四个函数,必要时捕获任何错误,然后调用完成处理程序。我们所要做的就是实现这四个功能!我们开始吧createCaptureSession

创建捕获会话

配置给定之前AVCaptureSession,我们需要创建它!将以下属性添加到您的CameraController.swift文件中:

var captureSession: AVCaptureSession?

接下来,将以下内容添加到createCaptureSession嵌套在您的函数的主体中prepare

self.captureSession = AVCaptureSession()

这是简单的代码; 它只是创建一个新的AVCaptureSession并将其存储在captureSession属性中。

现在我们已经创建了一个AVCaptureSession,我们需要创建AVCaptureDevice对象来表示实际的iOS设备的摄像头。继续并将以下属性添加到您的CameraController班级。我们现在要添加frontCamerarearCamera属性,因为我们将设置多摄像头捕获的基础知识,并实现稍后更改摄像头的功能。

1 var frontCamera: AVCaptureDevice?
2 var rearCamera: AVCaptureDevice?

接下来,在里面声明一个嵌入的类型CameraController.swift。我们将使用此嵌入式类型来管理创建捕获会话时可能遇到的各种错误:

enum CameraControllerError: Swift.Error {
        case captureSessionAlreadyRunning
        case captureSessionIsMissing
        case inputsAreInvalid
        case invalidOperation
        case noCamerasAvailable
        case unknown
    }

现在谈到有趣的部分!让我们找到设备上可用的相机。我们可以这样做AVCaptureDeviceDiscoverySession。将以下内容添加到configureCaptureDevices

 1 //1
 2 let session = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: .unspecified)
 3 guard let cameras = (session?.devices.flatMap { $0 }), !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
 4
 5 //2
 6 for camera in cameras {
 7     if camera.position == .front {
 8         self.frontCamera = camera
 9     }
10
11     if camera.position == .back {
12         self.rearCamera = camera
13
14         try camera.lockForConfiguration()
15         camera.focusMode = .continuousAutoFocus
16         camera.unlockForConfiguration()
17     }
18 }

这就是我们刚刚做的:

  1. 这两行代码用于AVCaptureDeviceDiscoverySession查找当前设备上可用的所有广角相机,并将其转换为非可选AVCaptureDevice实例的数组。如果没有相机可用,我们会抛出一个错误。
  2. 该循环通过代码段1中找到的可用摄像头进行查看,并确定哪个是前摄像头,哪个是后摄像头。它还将后置摄像头配置为自动对焦,并抛出沿途遇到的任何错误。

我们曾经AVCaptureDeviceDiscoverySession在设备上找到可用的摄像机,并将其配置为符合我们的规格。让我们将它们连接到我们的捕获会话。

配置设备输入

现在我们可以创建捕获设备输入,捕获设备并将它们连接到我们的捕获会话。在我们这样做之前,添加以下属性CameraController以确保我们可以存储我们的输入:

1 var currentCameraPosition: CameraPosition?
2 var frontCameraInput: AVCaptureDeviceInput?
3 var rearCameraInput: AVCaptureDeviceInput?

我们的代码不会在这个状态下编译,因为CameraPosition没有定义。我们来定义它。将其添加为以下内嵌类型CameraController

1 public enum CameraPosition {
2     case front
3     case rear
4 }

现在,我们拥有存储和管理捕获设备输入的所有必要特性。我们来实现configureDeviceInputs

 1 func configureDeviceInputs() throws {
 2     //3
 3     guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
 4
 5     //4
 6     if let rearCamera = self.rearCamera {
 7         self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
 8
 9         if captureSession.canAddInput(self.rearCameraInput!) { captureSession.addInput(self.rearCameraInput!) }
10
11         self.currentCameraPosition = .rear
12     }
13
14     else if let frontCamera = self.frontCamera {
15         self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
16
17         if captureSession.canAddInput(self.frontCameraInput!) { captureSession.addInput(self.frontCameraInput!) }
18         else { throw CameraControllerError.inputsAreInvalid }
19
20         self.currentCameraPosition = .front
21     }
22
23     else { throw CameraControllerError.noCamerasAvailable }
24 }

以下是我们所做的:

  1. 这条线只是确保captureSession存在。如果没有,我们会抛出一个错误。
  2. 这些if声明负责创建必要的捕捉设备输入以支持照片捕捉。AVFoundation每次捕捉会话只允许一个基于摄像头的输入。由于后置摄像头传统上是默认的,因此我们尝试从中创建输入并将其添加到捕获会话中。如果失败了,我们会回到前置摄像头。如果失败了,我们会抛出一个错误。

配置照片输出

直到这一点,我们已经添加了所有必要的输入captureSession。现在我们只需要一种方法捕获会话中获取必要的数据。幸运的是,我们有AVCapturePhotoOutput。添加一个属性到CameraController

var photoOutput: AVCapturePhotoOutput?

现在,我们来实现configurePhotoOutput这个:

 1 func configurePhotoOutput() throws {
 2     guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
 3
 4     self.photoOutput = AVCapturePhotoOutput()
 5     self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey : AVVideoCodecJPEG])], completionHandler: nil)
 6
 7     if captureSession.canAddOutput(self.photoOutput) { captureSession.addOutput(self.photoOutput) }
 8
 9     captureSession.startRunning()
10 }

这是一个简单的实现。它只是配置photoOutput,告诉它使用JPEG文件格式的视频编解码器。然后,它增加photoOutputcaptureSession。最后,它开始captureSession

我们差不多完成了!你的CameraController.swift文件应该看起来像这样:

  1 import AVFoundation
  2
  3 class CameraController {
  4     var captureSession: AVCaptureSession?
  5
  6     var currentCameraPosition: CameraPosition?
  7
  8     var frontCamera: AVCaptureDevice?
  9     var frontCameraInput: AVCaptureDeviceInput?
 10
 11     var photoOutput: AVCapturePhotoOutput?
 12
 13     var rearCamera: AVCaptureDevice?
 14     var rearCameraInput: AVCaptureDeviceInput?
 15 }
 16
 17 extension CameraController {
 18     func prepare(completionHandler: @escaping (Error?) -> Void) {
 19         func createCaptureSession() {
 20             self.captureSession = AVCaptureSession()
 21         }
 22
 23         func configureCaptureDevices() throws {
 24             let session = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: .unspecified)
 25             guard let cameras = (session?.devices.flatMap { $0 }), !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
 26
 27             for camera in cameras {
 28                 if camera.position == .front {
 29                     self.frontCamera = camera
 30                 }
 31
 32                 if camera.position == .back {
 33                     self.rearCamera = camera
 34
 35                     try camera.lockForConfiguration()
 36                     camera.focusMode = .autoFocus
 37                     camera.unlockForConfiguration()
 38                 }
 39             }
 40         }
 41
 42         func configureDeviceInputs() throws {
 43             guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
 44
 45             if let rearCamera = self.rearCamera {
 46                 self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
 47
 48                 if captureSession.canAddInput(self.rearCameraInput!) { captureSession.addInput(self.rearCameraInput!) }
 49
 50                 self.currentCameraPosition = .rear
 51             }
 52
 53             else if let frontCamera = self.frontCamera {
 54                 self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
 55
 56                 if captureSession.canAddInput(self.frontCameraInput!) { captureSession.addInput(self.frontCameraInput!) }
 57                 else { throw CameraControllerError.inputsAreInvalid }
 58
 59                 self.currentCameraPosition = .front
 60             }
 61
 62             else { throw CameraControllerError.noCamerasAvailable }
 63         }
 64
 65         func configurePhotoOutput() throws {
 66             guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
 67
 68             self.photoOutput = AVCapturePhotoOutput()
 69             self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey : AVVideoCodecJPEG])], completionHandler: nil)
 70
 71             if captureSession.canAddOutput(self.photoOutput) { captureSession.addOutput(self.photoOutput) }
 72             captureSession.startRunning()
 73         }
 74
 75         DispatchQueue(label: "prepare").async {
 76             do {
 77                 createCaptureSession()
 78                 try configureCaptureDevices()
 79                 try configureDeviceInputs()
 80                 try configurePhotoOutput()
 81             }
 82
 83             catch {
 84                 DispatchQueue.main.async {
 85                     completionHandler(error)
 86                 }
 87
 88                 return
 89             }
 90
 91             DispatchQueue.main.async {
 92                 completionHandler(nil)
 93             }
 94         }
 95     }
 96 }
 97
 98 extension CameraController {
 99     enum CameraControllerError: Swift.Error {
100         case captureSessionAlreadyRunning
101         case captureSessionIsMissing
102         case inputsAreInvalid
103         case invalidOperation
104         case noCamerasAvailable
105         case unknown
106     }
107
108     public enum CameraPosition {
109         case front
110         case rear
111     }
112 }

注意:我使用扩展来适当地分割代码。您可以根据个人的编码风格定义,但我认为这是一个很好的做法,因为它使您的代码更易于读写。

显示预览

现在我们已经准备好相机设备了,现在是时候显示它在屏幕上捕捉的内容了。添加另一个函数CameraController(在prepare)之外,称之为displayPreview。它应该有以下签名:

func displayPreview(on view: UIView) throws { }

另外,import UIKit在你的CameraController.swift文件中。我们将需要它来处理UIView

顾名思义,这个函数将负责创建一个捕获预览并在提供的视图上显示它。让我们添加一个属性CameraController来支持这个功能:

var previewLayer: AVCaptureVideoPreviewLayer?

该属性将保存显示输出的预览图层captureSession。我们来实现这个方法:

 1 func displayPreview(on view: UIView) throws {
 2     guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
 3
 4     self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
 5     self.previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
 6     self.previewLayer?.connection?.videoOrientation = .portrait
 7
 8     view.layer.insertSublayer(self.previewLayer!, at: 0)
 9     self.previewLayer?.frame = view.frame
10 }

此功能创建一个AVCaptureVideoPreview使用captureSession,将其设置为纵向,并将其添加到提供的视图。

接线

现在,让我们尝试将所有这些连接到我们的视图控制器。头向上ViewController.swift。首先,添加一个属性ViewController.swift

let cameraController = CameraController()

然后,添加一个嵌套函数viewDidLoad():

 1 func configureCameraController() {
 2     cameraController.prepare {(error) in
 3         if let error = error {
 4             print(error)
 5         }
 6
 7         try? self.cameraController.displayPreview(on: self.capturePreviewView)
 8     }
 9 }
10
11 configureCameraController()

这个功能简单地准备我们的相机控制器,就像我们设计的那样。

不幸的是,我们还有一步。这是苹果强制执行的安全要求。你必须为用户提供一个理由,解释为什么你的应用程序需要使用相机。打开Info.plist并插入一行:

Privacy - Camera Usage Description

这个键告诉用户当它要求必要的权限时为什么使用相机。

你的ViewController.swift文件现在应该是这样的:

 1 import UIKit
 2
 3 class ViewController: UIViewController {
 4     let cameraController = CameraController()
 5
 6     @IBOutlet fileprivate var captureButton: UIButton!
 7
 8     ///Displays a preview of the video output generated by the device‘s cameras.
 9     @IBOutlet fileprivate var capturePreviewView: UIView!
10
11     ///Allows the user to put the camera in photo mode.
12     @IBOutlet fileprivate var photoModeButton: UIButton!
13     @IBOutlet fileprivate var toggleCameraButton: UIButton!
14     @IBOutlet fileprivate var toggleFlashButton: UIButton!
15
16     ///Allows the user to put the camera in video mode.
17     @IBOutlet fileprivate var videoModeButton: UIButton!
18
19     override var prefersStatusBarHidden: Bool { return true }
20 }
21
22 extension ViewController {
23     override func viewDidLoad() {
24         func configureCameraController() {
25             cameraController.prepare {(error) in
26                 if let error = error {
27                     print(error)
28                 }
29
30                 try? self.cameraController.displayPreview(on: self.capturePreviewView)
31             }
32         }
33
34         func styleCaptureButton() {
35             captureButton.layer.borderColor = UIColor.black.cgColor
36             captureButton.layer.borderWidth = 2
37
38             captureButton.layer.cornerRadius = min(captureButton.frame.width, captureButton.frame.height) / 2
39         }
40
41         styleCaptureButton()
42         configureCameraController()
43     }
44 }

编译并运行你的项目,当系统提示是否允许使用相机时候点击允许,然后你应该有一个工作捕捉预览。如果没有,请重新检查您的代码,如果您需要帮助,请留下评论。

没错,今天深圳的天气还是挺好的.北方的小伙伴你们那里天气好吗? ??????

切换闪光/切换摄像机

现在我们有一个工作预览,让我们添加更多的功能。大多数相机应用程序允许用户切换相机并启用或禁用闪光灯。让我们也做这个。我们这样做后,我们将添加捕捉图像并将其保存到相机胶卷的功能。

首先,我们将启用切换闪光灯的功能。将此属性添加到CameraController:

var flashMode = AVCaptureFlashMode.off

现在,转到ViewController。添加一个@IBAction func切换闪光灯:

 1 @IBAction func toggleFlash(_ sender: UIButton) {
 2     if cameraController.flashMode == .on {
 3         cameraController.flashMode = .off
 4         toggleFlashButton.setImage(#imageLiteral(resourceName: "Flash Off Icon"), for: .normal)
 5     }
 6
 7     else {
 8         cameraController.flashMode = .on
 9         toggleFlashButton.setImage(#imageLiteral(resourceName: "Flash On Icon"), for: .normal)
10     }
11 }

现在,这就是我们所要做的。我们的CameraController类将处理闪光灯时,我们捕捉图像。让我们继续转换摄像机。

在AV基础上切换摄像头是一件非常容易的事情。我们只需要删除现有摄像头的捕捉输入,并为我们要切换的摄像头添加一个新的捕捉输入。让我们添加另一个功能,我们的CameraController 切换摄像机:

func switchCameras() throws { }

当我们切换摄像头时,我们要么切换到前置摄像头,要么切换到后置摄像头。所以,我们在下面声明2个嵌套函数switchCameras

1 func switchToFrontCamera() throws { }
2 func switchToRearCamera() throws { }

现在,添加以下内容switchCameras()

 1 //5
 2 guard let currentCameraPosition = currentCameraPosition, let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
 3
 4 //6
 5 captureSession.beginConfiguration()
 6
 7 func switchToFrontCamera() throws { }
 8 func switchToRearCamera() throws { }
 9
10 //7
11 switch currentCameraPosition {
12 case .front:
13     try switchToRearCamera()
14
15 case .rear:
16     try switchToFrontCamera()
17 }
18
19 //8
20 captureSession.commitConfiguration()

这就是我们刚刚做的:

  1. guard声明确保在尝试切换摄像机之前我们有一个有效的正在运行的捕获会话。它还验证是否有一个当前活动的摄像头。
  2. 这一行告诉捕获会话开始配置。
  3. 这条switch语句调用switchToRearCamera或者switchToFrontCamera,取决于哪个摄像头当前处于活动状态。
  4. 这条线在配置之后提交或保存我们的捕获会话。

我们现在要做的就是实现switchToFrontCameraswitchToRearCamera

 1 func switchToFrontCamera() throws {
 2     guard let inputs = captureSession.inputs as? [AVCaptureInput], let rearCameraInput = self.rearCameraInput, inputs.contains(rearCameraInput),
 3         let frontCamera = self.frontCamera else { throw CameraControllerError.invalidOperation }
 4
 5     self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
 6
 7     captureSession.removeInput(rearCameraInput)
 8
 9     if captureSession.canAddInput(self.frontCameraInput!) {
10         captureSession.addInput(self.frontCameraInput!)
11
12         self.currentCameraPosition = .front
13     }
14
15     else { throw CameraControllerError.invalidOperation }
16 }
17
18 func switchToRearCamera() throws {
19     guard let inputs = captureSession.inputs as? [AVCaptureInput], let frontCameraInput = self.frontCameraInput, inputs.contains(frontCameraInput),
20         let rearCamera = self.rearCamera else { throw CameraControllerError.invalidOperation }
21
22     self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
23
24     captureSession.removeInput(frontCameraInput)
25
26     if captureSession.canAddInput(self.rearCameraInput!) {
27         captureSession.addInput(self.rearCameraInput!)
28
29         self.currentCameraPosition = .rear
30     }
31
32     else { throw CameraControllerError.invalidOperation }
33 }

两个函数都有非常相似的实现。他们首先获得捕获会话中所有输入的数组,并确保可以切换到请求相机。接下来,他们创建必要的输入设备,删除旧的,并添加新的。最后,他们设定currentCameraPositionCameraController班级知道变化。简单!回到ViewController.swift我们可以添加一个功能来切换相机:

 1 @IBAction func switchCameras(_ sender: UIButton) {
 2     do {
 3         try cameraController.switchCameras()
 4     }
 5
 6     catch {
 7         print(error)
 8     }
 9
10     switch cameraController.currentCameraPosition {
11     case .some(.front):
12         toggleCameraButton.setImage(#imageLiteral(resourceName: "Front Camera Icon"), for: .normal)
13
14     case .some(.rear):
15         toggleCameraButton.setImage(#imageLiteral(resourceName: "Rear Camera Icon"), for: .normal)
16
17     case .none:
18         return
19     }
20 }

打开你的故事板,连接必要的插座,并构建和运行应用程序。你应该可以自由切换相机。现在我们来实现最重要的功能:图像捕捉!

实现图像捕捉

现在我们可以实现我们一直在等待的功能:图像捕捉。在我们进入之前,让我们快速回顾一下迄今为止所做的一切:

  • 设计了一个可用于轻松隐藏AV基金会复杂性的实用工具类。
  • 在这个类中实现了功能,允许我们创建一个捕捉会话,使用闪光灯,切换摄像头,并获得工作预览。
  • 连接我们的课程,UIViewController并建立一个轻量级的相机应用程序。

我们所要做的只是捕捉图像!

打开CameraController.swift,让我们开始工作。captureImage用这个签名添加一个函数:

1 func captureImage(completion: (UIImage?, Error?) -> Void) {
2
3 }

顾名思义,这个功能将会使用我们制作的相机控制器为我们拍摄一张图像。让我们来实现它:

1 func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
2     guard let captureSession = captureSession, captureSession.isRunning else { completion(nil, CameraControllerError.captureSessionIsMissing); return }
3
4     let settings = AVCapturePhotoSettings()
5     settings.flashMode = self.flashMode
6
7     self.photoOutput?.capturePhoto(with: settings, delegate: self)
8     self.photoCaptureCompletionBlock = completion
9 }

这不是一个复杂的实现,但我们的代码还不能编译,因为我们没有定义photoCaptureCompletionBlockCameraController不符合AVCapturePhotoCaptureDelegate。首先,我们添加一个属性photoCaptureCompletionBlockCameraController

var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?

现在,我们来扩展CameraController以符合AVCapturePhotoCaptureDelegate:

 1 extension CameraController: AVCapturePhotoCaptureDelegate {
 2     public func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?,
 3                         resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) {
 4         if let error = error { self.photoCaptureCompletionBlock?(nil, error) }
 5
 6         else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil),
 7             let image = UIImage(data: data) {
 8
 9             self.photoCaptureCompletionBlock?(image, nil)
10         }
11
12         else {
13             self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
14         }
15     }
16 }

现在回头再来ViewController一次。首先,导入Photos框架,因为我们将使用内置的API来保存照片。

import Photos

然后插入以下函数:

 1 @IBAction func captureImage(_ sender: UIButton) {
 2     cameraController.captureImage {(image, error) in
 3         guard let image = image else {
 4             print(error ?? "Image capture error")
 5             return
 6         }
 7
 8         try? PHPhotoLibrary.shared().performChangesAndWait {
 9             PHAssetChangeRequest.creationRequestForAsset(from: image)
10         }
11     }
12 }

我们只需调用captureImage相机控制器的方法来拍摄照片,然后使用PHPhotoLibary该类将图像保存到内置的照片库中。

最后,连接@IBAction func到故事板中的捕获按钮,然后Info.plist转到插入一行:

Privacy - Camera Usage Description

这是iOS 10中引入的隐私要求。您必须指定您的应用程序需要访问照片库的原因。

现在建立并运行应用程序来捕捉照片!之后,打开你的照片库。你应该看到你刚刚拍摄的照片。恭喜,你现在知道如何在您的应用程序中使用AV基金会!祝你好运,并继续关注本教程的第二部分,我们将学习如何捕获视频。

对于完整的项目,你可以点击下载

特此声明:以上所有信息仅仅提供学习使用, 部分功能如果可以帮助你解决项目中的问题, 我也很开心. 后面我也会将录制视频的功能完善上去.

一个就职于汽车物联网公司的 iOS 开发者

时间: 2024-11-13 18:09:34

使用AVFoundation完成照片拍摄存储相册, 开启关闭闪光灯, 切换摄像头的相关文章

iOS开发之保存照片到系统相册(Photo Album)

iOS开发之保存照片到系统相册(Photo Album) 保存照片到系统相册这个功能很多社交类的APP都有的,今天我们简单讲解一下,如何将图片保存到系统相册(Photo Album). 创建UIImageView 创建UIImageView是为了将照片展示出来,我们是要把UIImage保存到系统相册(Photo Album): #define SCREEN [UIScreen mainScreen].bounds.size self.image = [UIImage imageNamed:@"i

iOS开发>学无止境 - 保存照片到系统相册(Photo Album)

保存照片到系统相册这个功能很多社交类的APP都有的,今天我们简单讲解一下,如何将图片保存到系统相册(Photo Album). 创建UIImageView 创建UIImageView是为了将照片展示出来,我们是要把UIImage保存到系统相册(Photo Album): #define SCREEN [UIScreen mainScreen].bounds.size self.image = [UIImage imageNamed:@"iOSDevTip"]; UIImageView

Windows编程 - 开启/关闭/遍历程序的类 代码(C++)

开启/关闭/遍历程序的类 代码(C++) 本文地址: http://blog.csdn.net/caroline_wendy 类包含4个函数, 启动程序, 遍历所有进程, 关闭程序, 遍历进程依赖的动态链接库. 示例: Image.exe是预先生成的可执行程序(exe), 启动程序, 间隔5秒, 关闭程序. 使用方法参加测试程序. 代码: /* * process.h * * Created on: 2014.06.08 * Author: Spike */ /*vs 2012*/ #ifnde

Linux下开启关闭防火墙

一.Linux下开启/关闭防火墙命令 1) 永久性生效,重启后不会复原 开启: chkconfig iptables on 关闭: chkconfig iptables off 2) 即时生效,重启后复原 开启: /etc/init.d/iptables start 关闭: /etc/init.d/iptables stop 需要说明的是对于Linux下的其它服务都可以用以上命令执行开启和关闭操作. 在当开启了防火墙时,做如下设置,开启相关端口, 修改/etc/sysconfig/iptable

LINUX服务开启关闭建议

1.操作系统centos5.5 服务名称 功能 默认 建议 备注 NetworkManager 用于自动连接网络 关闭 关闭 对服务器没用 acpid 电源的开关等检测管理 开启 关闭 对服务器没用 anacron 一种计划任务管理 开启 开启 apmd 高级电源管理 开启 开启 atd 在指定时间执行命令 开启 关闭 如果用crond,则可关闭它 auditd 开启 自定 如果用selinux,需要开启它 autofs 文件系统自动加载.卸载 开启 自定 只在需要时开启它,可以停止 avahi

MySQL5.7在线开启/关闭GTID

MySQL5.7在线开启/关闭GTID 环境介绍 Part1:写在最前 截止本文撰写当日,MySQL5.7.16是官网的最新稳定版,本文将用MySQL5.7.16来进行演示.从MySQL5.6开始,支持了GTID复制模式,这种模式其实是把双刃剑,虽然容易搭建主从复制了,但使用不当,就容易出现一些错误,例如error 1236.在MySQL5.6如果开启GTID模式,需要在my.cnf中加入以下几个参数: ①log-bin=mysql-bin ②binlog_format=row ③log_sla

CentOS开启关闭防火墙

centos开启和关闭防火墙命令(1)临时生效,重启后还原开启: # service iptables start关闭: # service iptables stop(2)永久性关闭,重启后不会还原开启: # chkconfig iptables on关闭: # chkconfig iptables off CentOS开启关闭防火墙,布布扣,bubuko.com

Android -- Service的开启关闭与生命周期

Service是Android 系统中的四大组件之一,是在一段不定的时间运行在后台,不和用户交互应用组件. service可以在很多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化等等. 生命周期                                                                                  context.startService() 启动流程: con

Web服务器管理系列:12、开启关闭Ping命令

有些时候网站打开速度会很慢,我们要排查故障所在,需要用到Ping命令,但是Windows Server 2008防火墙默认是关闭Ping的 我们可以通过以下方法开启Ping 打开防火墙->高级安全: 选择文件和打印机共享(回显请求 - ICMPv4-In) 选择启用: 记得用完Ping后一定要将其关闭,因为DDos攻击就是借助于Ping 这里解释一下: ping 一个发送测试数据包检测网络状况的命令 -l 65500 发送包含65500字节数据量的ECHO数据包 -n 100 发送100遍ECH