iOS 10 的 Photography

iOS 10中新特性:

  • 有史以來第一次雳灵,你的app 可以拍攝和編輯 live photos
  • 可以響應不同的圖片捕獲

一喉镰、相機捕獲內容顯示(輸入)

  1. 創(chuàng)建名為PhotoMe并設置只有IPhone使用的項目签杈,豎屏规脸。
  2. 授權:在Info.plist 中添加
<key>NSCameraUsageDescription</key>
    <string>PhotoMe needs the camera to take photos. Duh!</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>PhotoMe needs the microphone to record audio with Live Photos.</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>PhotoMe will save photos in the Photo Library.</string>
  1. 自定義View:創(chuàng)建名為CameraPreviewView 的UIView子類
import UIKit
import AVFoundation
import Photos
class CameraPreviewView: UIView {
    //1
    override class var layerClass: AnyClass {
        return AVCaptureVideoPreviewLayer.self
    }
    //2
    var cameraPreviewLayer: AVCaptureVideoPreviewLayer {
        return layer as! AVCaptureVideoPreviewLayer
    }
    //3
    var session: AVCaptureSession? {
        get {
            return cameraPreviewLayer.session
        }
        set {
            cameraPreviewLayer.session = newValue
        }
    } 
}
  1. 畫界面:在Main.storyboard 中翘狱,拖入一個UIView秘案,并設定約束,上潦匈,左阱高,右和父視圖對齊,寬高比為3:4茬缩,設置自定義類為CameraPreviewView赤惊,向ViewController連接名為cameraPreviewView的outlet屬性。
  2. 在ViewController中:
import AVFoundation

添加屬性:

    fileprivate let session = AVCaptureSession()
    fileprivate let sessionQueue = DispatchQueue(label: "com.razeware.PhotoMe.session-queue")
    var videoDeviceInput: AVCaptureDeviceInput!

添加方法:

    private func prepareCaptureSession() {
        // 1
        session.beginConfiguration()
        session.sessionPreset = AVCaptureSessionPresetPhoto
        
        do {
            // 2
            let videoDevice = AVCaptureDevice.defaultDevice(
                withDeviceType: .builtInWideAngleCamera,
                mediaType: AVMediaTypeVideo,
                position: .front)
            // 3
            let videoDeviceInput = try
                AVCaptureDeviceInput(device: videoDevice)
            
            // 4
            if session.canAddInput(videoDeviceInput) {
                session.addInput(videoDeviceInput)
                self.videoDeviceInput = videoDeviceInput
                
                // 5
                DispatchQueue.main.async {
                    self.cameraPreviewView.cameraPreviewLayer
                        .connection.videoOrientation = .portrait
                }
            } else {
                print("Couldn't add device to the session")
                return
            }
        } catch {
            print("Couldn't create video device input: \(error)")
            return
        }
        
        // 6
        session.commitConfiguration()
    }

在viewDidLoad() 中,授權凰锡,配置session:

        //1
        cameraPreviewView.session = session
        //2
        sessionQueue.suspend()
        //3
        AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) {
            success in
            if !success {
                print("Come on, it's a camera app!")
                return
            }
            //4
            self.sessionQueue.resume()
        }
        
        sessionQueue.async {
            [unowned self] in
            self.prepareCaptureSession()
        }

在viewWillAppear() 中,啟動session:

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        sessionQueue.async {
            self.session.startRunning()
        }
    }

二未舟、拍照(輸出)

  1. 在ViewController 中:
    添加屬性:
fileprivate let photoOutput = AVCapturePhotoOutput()

prepareCaptureSession()中,prepareCaptureSession()前面:

 if session.canAddOutput(photoOutput) {
     session.addOutput(photoOutput)
     // 必須在session啟動之前配置掂为,否則會發(fā)生閃爍
     photoOutput.isHighResolutionCaptureEnabled = true
 } else {
     print("Unable to add photo output")
     return
 }
  1. 畫界面:
  2. 在Main.storyboard 中設置main view 和 camera preview view 的background 為black裕膀,main view的tint color 為 orange。
  3. main view拖入Visual Effect View With Blur,設置約束為左勇哗,下昼扛,右與父視圖對齊。 Blur Style 為 Dark欲诺。
  4. Visual Effect View With Blur 拖入 button野揪,名稱為Take Photo!,字體為20,并將其放入stack View 中瞧栗。
  5. 對stack View 添加頂部距父視圖為5,底部距父視圖為20的約束海铆。
  6. 連接button到ViewController 中:
@IBOutlet weak var shutterButton: UIButton!
@IBAction func handleShutterButtonTap(_ sender: UIButton) {
        capturePhoto()
}
extension ViewController {
    fileprivate func capturePhoto() {
        // 1
        let cameraPreviewLayerOrientation = cameraPreviewView
            .cameraPreviewLayer.connection.videoOrientation
        
        // 2
        sessionQueue.async {
            if let connection = self.photoOutput
                .connection(withMediaType: AVMediaTypeVideo) {
                connection.videoOrientation =
                cameraPreviewLayerOrientation
            }
            
            // 3
            let photoSettings = AVCapturePhotoSettings()
            photoSettings.flashMode = .off
            photoSettings.isHighResolutionPhotoEnabled = true
            
        }
    }
}
  1. 創(chuàng)建代理:因為一張照片還沒處理完就可以拍另一張迹恐,所以ViewController 持有一個鍵為AVCapturePhotoSettings,值為代理的字典卧斟。
import AVFoundation
import Photos
class PhotoCaptureDelegate: NSObject {
    // 1
    var photoCaptureBegins: (() -> ())? = .none
    var photoCaptured: (() -> ())? = .none
    fileprivate let completionHandler: (PhotoCaptureDelegate, PHAsset?)-> ()
    
    // 2
    fileprivate var photoData: Data? = .none
    
    // 3
    init(completionHandler: @escaping (PhotoCaptureDelegate, PHAsset?) -> ()) {
        self.completionHandler = completionHandler
    }
    
    // 4
    fileprivate func cleanup(asset: PHAsset? = .none) {
        completionHandler(self, asset)
    }
}
  1. 原理如圖:


    photo capture.png
  2. 實現(xiàn)代理方法
extension PhotoCaptureDelegate: AVCapturePhotoCaptureDelegate {
    // Process data completed
    func capture(_ captureOutput: AVCapturePhotoOutput,
                 didFinishProcessingPhotoSampleBuffer
        photoSampleBuffer: CMSampleBuffer?,
                 previewPhotoSampleBuffer: CMSampleBuffer?,
                 resolvedSettings: AVCaptureResolvedPhotoSettings,
                 bracketSettings: AVCaptureBracketedStillImageSettings?,
                 error: Error?) {
        
        guard let photoSampleBuffer = photoSampleBuffer else {
            print("Error capturing photo \(error)")
            return
        }
        photoData = AVCapturePhotoOutput
            .jpegPhotoDataRepresentation(
                forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer)
    }
    
    // Entire process completed
    func capture(_ captureOutput: AVCapturePhotoOutput,
                 didFinishCaptureForResolvedSettings
        resolvedSettings: AVCaptureResolvedPhotoSettings,
                 error: Error?) {
        
        // 1
        guard error == nil, let photoData = photoData else {
            print("Error \(error) or no data")
            cleanup()
            return
        }
        
        // 2
        PHPhotoLibrary.requestAuthorization {
            [unowned self]
            (status) in
            // 3
            guard status == .authorized else {
                print("Need authorisation to write to the photo library")
                self.cleanup()
                return
            }
            // 4
            var assetIdentifier: String?
            PHPhotoLibrary.shared().performChanges({
                let creationRequest = PHAssetCreationRequest.forAsset()
                let placeholder = creationRequest
                    .placeholderForCreatedAsset
                
                creationRequest.addResource(with: .photo,
                                            data: photoData, options: .none)
                
                assetIdentifier = placeholder?.localIdentifier
                
            }, completionHandler: { (success, error) in
                if let error = error {
                    print("Error saving to the photo library: \(error)")
                }
                var asset: PHAsset? = .none
                if let assetIdentifier = assetIdentifier {
                    asset = PHAsset.fetchAssets(
                        withLocalIdentifiers: [assetIdentifier],
                        options: .none).firstObject
                }
                self.cleanup(asset: asset)
            })
        }
    }
}
  1. 在ViewController中:
    添加屬性:
fileprivate var photoCaptureDelegates = [Int64 : PhotoCaptureDelegate]()

在capturePhoto()的queue 閉包末尾添加:

            // 1
            let uniqueID = photoSettings.uniqueID
            let photoCaptureDelegate = PhotoCaptureDelegate() {
                [unowned self] (photoCaptureDelegate, asset) in
                self.sessionQueue.async { [unowned self] in
                    self.photoCaptureDelegates[uniqueID] = .none
                }
            }
            // 2
            self.photoCaptureDelegates[uniqueID] = photoCaptureDelegate
            // 3
            self.photoOutput.capturePhoto(
                with: photoSettings, delegate: photoCaptureDelegate)

三殴边、難以置信的

  1. 添加閃屏動畫
    在capturePhoto()創(chuàng)建delegate 后,添加:
    photoCaptureDelegate.photoCaptureBegins = { [unowned self] in
    DispatchQueue.main.async {
    self.shutterButton.isEnabled = false
    self.cameraPreviewView.cameraPreviewLayer.opacity = 0
    UIView.animate(withDuration: 0.2) {
    self.cameraPreviewView.cameraPreviewLayer.opacity = 1
    }
    }
    }
    photoCaptureDelegate.photoCaptured = { [unowned self] in
    DispatchQueue.main.async {
    self.shutterButton.isEnabled = true
    }
    }
    在PhotoCaptureDelegate的extension 中:
    func capture(_ captureOutput: AVCapturePhotoOutput,
                 willCapturePhotoForResolvedSettings
        resolvedSettings: AVCaptureResolvedPhotoSettings) {
        photoCaptureBegins?()
    }
    func capture(_ captureOutput: AVCapturePhotoOutput,
                 didCapturePhotoForResolvedSettings
        resolvedSettings: AVCaptureResolvedPhotoSettings) {
        photoCaptured?()
    }
  1. 展示縮略圖
  • 獲取縮略圖:在PhotoCaptureDelegate添加屬性:
var thumbnailCaptured: ((UIImage?) -> ())? = .none

在代理方法didFinishProcessingPhotoSampleBuffer末尾添加:

        if let thumbnailCaptured = thumbnailCaptured,
            let previewPhotoSampleBuffer = previewPhotoSampleBuffer,
            let cvImageBuffer =
            CMSampleBufferGetImageBuffer(previewPhotoSampleBuffer) {
            
            let ciThumbnail = CIImage(cvImageBuffer: cvImageBuffer)
            let context = CIContext(options: [kCIContextUseSoftwareRenderer:
                false])
            let thumbnail = UIImage(cgImage: context.createCGImage(ciThumbnail,
                                                                   from: ciThumbnail.extent)!, scale: 2.0, orientation: .right)
            
            thumbnailCaptured(thumbnail)
        }
  • 畫界面:設置switch 為 off珍语,label 的文本顏色為white锤岸,imageView 的 clip to bounds 為 true,content mode 為 Aspect Fill板乙。


    main storyboard.png

    UI.png

    連線:

    @IBOutlet weak var previewImageView: UIImageView!
    @IBOutlet weak var thumbnailSwitch: UISwitch!
  • 設置縮略圖格式: 在capturePhoto()中是偷,創(chuàng)建delegate之前添加:
            if self.thumbnailSwitch.isOn
                && photoSettings.availablePreviewPhotoPixelFormatTypes
                    .count > 0 {
                photoSettings.previewPhotoFormat = [
                    kCVPixelBufferPixelFormatTypeKey as String :
                        photoSettings
                            .availablePreviewPhotoPixelFormatTypes.first!,
                    kCVPixelBufferWidthKey as String : 160,
                    kCVPixelBufferHeightKey as String : 160
                ]
            }
  • 展示縮略圖:在創(chuàng)建delegate之后添加:
            photoCaptureDelegate.thumbnailCaptured = { [unowned self] image in
                DispatchQueue.main.async {
                    self.previewImageView.image = image
                }
            }
  1. Live Photos
  • 畫界面:在Option Stack 中拖入一Horizontal Stack View拳氢,嵌入一個switch 和 一個 label,設置switch 為 off蛋铆,label 的text 為 Live Photo Mode, text color 為 white馋评。向Control Stack 加入一個label ,text 為 capturing...刺啦,字體大小為35留特,hidden 為 true,text color 為 orange玛瘸。
    連線:
    @IBOutlet weak var capturingLabel: UILabel!
    @IBOutlet weak var livePhotoSwitch: UISwitch!
  • 添加音頻輸入:在prepareCaptureSession()中蜕青,創(chuàng)建video device input 之后添加:
        do {
            let audioDevice = AVCaptureDevice.defaultDevice(withMediaType:
                AVMediaTypeAudio)
            let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice)
            if session.canAddInput(audioDeviceInput) {
                session.addInput(audioDeviceInput)
            } else {
                print("Couldn't add audio device to the session")
                return
            }
        } catch {
            print("Unable to create audio device input: \(error)")
            return
        }
  • 設置Photo 輸出:在photoOutput.isHighResolutionCaptureEnabled = true 之后添加:
            photoOutput.isLivePhotoCaptureEnabled =
                photoOutput.isLivePhotoCaptureSupported
            DispatchQueue.main.async {
                self.livePhotoSwitch.isEnabled =
                    self.photoOutput.isLivePhotoCaptureSupported
  • 設置輸出路徑:在capturePhoto()中,創(chuàng)建delegate之前添加:
    if self.livePhotoSwitch.isOn {
    let movieFileName = UUID().uuidString
    let moviePath = (NSTemporaryDirectory() as NSString)
    .appendingPathComponent("(movieFileName).mov")
    photoSettings.livePhotoMovieFileURL = URL(
    fileURLWithPath: moviePath)
    }
  • 捕獲live photo:在PhotoCaptureDelegate添加屬性:
    var capturingLivePhoto: ((Bool) -> ())? = .none
    fileprivate var livePhotoMovieURL: URL? = .none

在代理方法willCapturePhotoForResolvedSettings末尾添加:

        if resolvedSettings.livePhotoMovieDimensions.width > 0
            && resolvedSettings.livePhotoMovieDimensions.height > 0 {
            capturingLivePhoto?(true)
        }

添加代理方法:

    func capture(_ captureOutput: AVCapturePhotoOutput,
                 didFinishRecordingLivePhotoMovieForEventualFileAt
        outputFileURL: URL,
                 resolvedSettings: AVCaptureResolvedPhotoSettings) {
        capturingLivePhoto?(false)
    }
    
    func capture(_ captureOutput: AVCapturePhotoOutput,
                 didFinishProcessingLivePhotoToMovieFileAt outputFileURL:
        URL,
                 duration: CMTime,
                 photoDisplay photoDisplayTime: CMTime,
                 resolvedSettings: AVCaptureResolvedPhotoSettings,
                 error: Error?) {
        if let error = error {
            print("Error creating live photo video: \(error)")
            return
        }
        livePhotoMovieURL = outputFileURL
    }
  • 添加視頻到相冊:在代理方法didFinishCaptureForResolvedSettings:error:)中糊渊,addResource 后添加:
                
                if let livePhotoMovieURL = self.livePhotoMovieURL {
                    let movieResourceOptions = PHAssetResourceCreationOptions()
                    movieResourceOptions.shouldMoveFile = true
                    creationRequest.addResource(with: .pairedVideo,
                                                fileURL: livePhotoMovieURL, options:
                        movieResourceOptions)
                }
  • 更新UI:在ViewController添加屬性:
fileprivate var currentLivePhotoCaptures: Int = 0

在capturePhoto()中右核,創(chuàng)建delegate之后添加:

            // Live photo UI updates
            photoCaptureDelegate.capturingLivePhoto = { (currentlyCapturing) in
                DispatchQueue.main.async { [unowned self] in
                    self.currentLivePhotoCaptures += currentlyCapturing ? 1 : -1
                    UIView.animate(withDuration: 0.2) {
                        self.capturingLabel.isHidden =
                            self.currentLivePhotoCaptures == 0
                    }
                }
            }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市再来,隨后出現(xiàn)的幾起案子蒙兰,更是在濱河造成了極大的恐慌,老刑警劉巖芒篷,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搜变,死亡現(xiàn)場離奇詭異,居然都是意外死亡针炉,警方通過查閱死者的電腦和手機挠他,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篡帕,“玉大人殖侵,你說我怎么就攤上這事×眨” “怎么了拢军?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵,是天一觀的道長怔鳖。 經常有香客問我茉唉,道長,這世上最難降的妖魔是什么结执? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任度陆,我火速辦了婚禮,結果婚禮上献幔,老公的妹妹穿的比我還像新娘懂傀。我一直安慰自己,他們只是感情好蜡感,可當我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布蹬蚁。 她就那樣靜靜地躺著恃泪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缚忧。 梳的紋絲不亂的頭發(fā)上悟泵,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天,我揣著相機與錄音闪水,去河邊找鬼糕非。 笑死,一個胖子當著我的面吹牛球榆,可吹牛的內容都是我干的朽肥。 我是一名探鬼主播,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼持钉,長吁一口氣:“原來是場噩夢啊……” “哼衡招!你這毒婦竟也來了?” 一聲冷哼從身側響起每强,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤始腾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后空执,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浪箭,經...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年辨绊,在試婚紗的時候發(fā)現(xiàn)自己被綠了奶栖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡门坷,死狀恐怖宣鄙,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情默蚌,我是刑警寧澤冻晤,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站绸吸,受9級特大地震影響明也,放射性物質發(fā)生泄漏。R本人自食惡果不足惜惯裕,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绣硝。 院中可真熱鬧蜻势,春花似錦、人聲如沸鹉胖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挠铲,卻和暖如春冕屯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拂苹。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工安聘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓢棒。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓浴韭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脯宿。 傳聞我的和親對象是個殘疾皇子念颈,可洞房花燭夜當晚...
    茶點故事閱讀 45,982評論 2 361

推薦閱讀更多精彩內容