アイフォンアプリ開発 カメラアプリ開発、第1日目 カメラ起動、保存失敗2017/08/27 カメラアプリ開発、第2日目 無音カメラに挑戦

アイフォンアプリ開発 カメラアプリ開発、第1日目 カメラ起動、保存失敗2017/08/27


を参考にしましたが、SWIFTのバージョンが違うので幾つか修正が必要

そして、そのままではクラッシュするので、
info.plistを

[iPhone] UIImagePickerController による Camera撮影




を参考にして
Privacy-Camera Usage Description カメラの撮影に必須です。(適宜変更して
Privary-Photo Library Usage Description アルバムの選択に必須です(適宜変更して
を追加します。
するとカメラが起動しますが、まだ保存でエラーになります
保存はできずにエラーになりますが、ちゃんとカメラが起動して撮影できるところまで動きますのですごいなぁと感心しました。

ViewControllerのソース。このままでは保存ができない。上記URLをコピペして適宜変更して掲載してます。

(保存するとエラーになるのを解消しました。原因は未だわかっていませんが、他のサイトのソースを参考にしたら保存できるようになりました。)

//
//  ViewController.swift
//  cameraTest20170827
//
//  Created by 間世田 俊朗 on 2017/08/27.
//  Copyright © 2017年 maseda. All rights reserved.
//
/*
 
 を参考にしましたが、SWIFTのバージョンが違うので幾つか修正が必要
 そして、そのままではクラッシュするので、
 info.plistを
 
[iPhone] UIImagePickerController による Camera撮影
を参考にして Privacy-Camera Usage Description カメラの撮影に必須です。(適宜変更して Privary-Photo Library Usage Description アルバムの選択に必須です(適宜変更して を追加します。 するとカメラが起動しますが、まだ保存でエラーになります */ import UIKit import AVFoundation class ViewController: UIViewController ,UIImagePickerControllerDelegate,UINavigationControllerDelegate{ @IBOutlet var imageView :UIImageView!//これを記述するとMain.stroyboardのOutletsにimageViewのアイコンが表示されます。 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // アラートを表示する func showAlert(title: String, message: String) { let alertView = UIAlertView() alertView.title = title alertView.message = message alertView.addButton(withTitle: "OK") alertView.show() } @IBAction func cameraStart(sender:AnyObject){ let sourceType:UIImagePickerControllerSourceType = UIImagePickerControllerSourceType.camera // カメラが利用可能かチェック if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera){ // インスタンスの作成 let cameraPicker = UIImagePickerController() cameraPicker.sourceType = sourceType cameraPicker.delegate = self self.present(cameraPicker, animated: true, completion: nil) } else{ showAlert(title: "", message: "Error of the camera function.") } } // 撮影が完了時した時に呼ばれる //こっちは動作するが func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { let image = info[UIImagePickerControllerOriginalImage] as! UIImage self.imageView.image = image //UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) self.dismiss(animated: true, completion: nil) } /* こっちは動作しない。なぜ? private func imagePickerController(imagePicker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage{ imageView.contentMode = .scaleAspectFit self.imageView.image = pickedImage } /* 次はこれを試してみて next here 2017/08/27 if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage { cameraView.contentMode = .scaleAspectFit//ここでimageView.contentMode = .scaleAspectFit//とします cameraView.image = pickedImage } */ //閉じる処理 imagePicker.dismiss(animated: true, completion: nil) } */ // 写真を保存 @IBAction func savePic(sender : AnyObject) { let image:UIImage! = imageView.image if image != nil { UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) //UIImageWriteToSavedPhotosAlbum(image, self, Selector(("image:didFinishSavingWithError:contextInfo:")), nil) } else{ showAlert(title: "", message: "image data is empty, image Failed !") } } // 撮影がキャンセルされた時に呼ばれる func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true, completion: nil) } // 書き込み完了結果の受け取り func image(image: UIImage, didFinishSavingWithError error: NSError!, contextInfo:UnsafeMutableRawPointer) { if error != nil { showAlert(title: "", message: "Failed to save the picture.") } else { showAlert(title: "", message: "The picture was saved.") } } }

カメラアプリ開発、第2日目 無音カメラに挑戦

アイフォンにシャッター音が無音のカメラをインストールして変な撮影をしようというわけではなくて、静かな雰囲気の中でカシャカシャとカメラのシャッター音がすると、周囲が何やってんだ?と思わんばかりにこちらを向くのが嫌で、アイフォンの音量を小さくしたりスピーカー部分を手で抑えて音量を下げたりしています。
実際に庭で撮影をしたら、隣の人が何を撮影してるんですか?と興味本位で聞いてきて少々びっくりしました。シャッター音って外であっても結構響いて聴こえるんですよね。単に庭の風景をなんとなく撮影していただけなのに、改めて聞かれると回答に困るのと折角の写真撮影の気分が台無しになってしまいました。

もしシャッター音が発生しないカメラなら、周囲に気にせずにアイフォンのカメラで撮影できます。

田舎の場合は周囲に女性がいないので変な撮影をすることはありません。

こちらのサイトを参考にしました。
でも現在Swift3.0だと、Swift1,2とかなりAPIに変更があって多くのエラーが表示されます。自動で補完してくれるコードもありますが、いくつかは自分で調べて修正する必要があります。
中には、廃止になったAPIもあれば、単に単語の最初の一文字を大文字か小文字に修正すれば良いものもありました。だいたい似たようなAPIに変わるのですが厄介なのが、引数が変わってしまうメソッドやAPIです。
こうなるとさっぱりわからんです。

今回のソースは、ストーリーボードに何も配置しないタイプです。
コード側から色々と設定できるタイプです。昔のアプリタイプですね。

//
//  ViewController.swift
//  cameraAVfound20170831
//
//  Created by 間世田 on 2017/08/31.
//  Copyright © 2017年 maseda. All rights reserved.
//

/*
 2017/08/31
 こっちはAVFOUNDATIONタイプのカメラアプリです。無音にできます。
 http://qiita.com/touyu/items/6fd26a35212e75f98c6bを参考にしました。
 でもバージョンが古いので色々と新しいswift3.0バージョンに書き換えました。XCODEが半分くらいは補完してくれますが
 それでもネットで修正箇所を探さないとエラーになってます。
 */

import UIKit
import AVFoundation

class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate, UIGestureRecognizerDelegate{
    var input:AVCaptureDeviceInput!
    var output:AVCaptureVideoDataOutput!
    var session:AVCaptureSession!
    var camera:AVCaptureDevice!
    var imageView:UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 画面タップでピントをあわせる
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.tappedScreen(sender:)))
        
        //let tapGesture = UITapGestureRecognizer(target: self, action: #selector(CameraViewController.tappedScreen(_:)))
        let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(ViewController.pinchedGesture(sender:)))
        
        // デリゲートをセット
        tapGesture.delegate = self
        
        // Viewにタップ、ピンチのジェスチャーを追加
        self.view.addGestureRecognizer(tapGesture)
        self.view.addGestureRecognizer(pinchGesture)
        
        let underView = UIView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: self.view.frame.size.width, height:self.view.frame.size.height/8)))
        underView.center = CGPoint(x: self.view.frame.size.width/2, y: self.view.frame.size.height-underView.frame.size.height/2)
        underView.backgroundColor = UIColor.black.withAlphaComponent(0.4)
        self.view.addSubview(underView)
        
        let shutterButton = UIButton(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: underView.frame.size.height-15, height: underView.frame.size.height-15)))
        shutterButton.center = CGPoint(x: underView.frame.size.width/2, y: underView.frame.size.height/2)
        shutterButton.backgroundColor = UIColor.white.withAlphaComponent(0)
        shutterButton.layer.masksToBounds = true
        shutterButton.layer.cornerRadius = shutterButton.frame.size.width/2
        shutterButton.layer.borderColor = UIColor.white.cgColor
        shutterButton.layer.borderWidth = 6
        shutterButton.addTarget(self, action: #selector(tapedShutterButton(sender:)), for: .touchUpInside)
        underView.addSubview(shutterButton)
        
        let shutterShadowView = UIView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: shutterButton.frame.size.height-18, height: shutterButton.frame.size.height-18)))
        shutterShadowView.center = CGPoint(x: shutterButton.frame.size.width/2, y: shutterButton.frame.size.height/2)
        shutterShadowView.backgroundColor = UIColor.white//UIColor.whiteColor()
        shutterShadowView.layer.masksToBounds = true
        shutterShadowView.layer.cornerRadius = shutterShadowView.frame.size.width/2
        // shutterShadowView.layer.borderColor = UIColor.blackColor().CGColor
        // shutterShadowView.layer.borderWidth = 3
        shutterShadowView.isUserInteractionEnabled = false
        shutterButton.addSubview(shutterShadowView)
        
        let closeButton = UIButton()
        closeButton.setTitle("閉じる", for:.normal)//.Normal
        closeButton.setTitleColor(UIColor.white, for: .normal)//.Normal
        closeButton.sizeToFit()
        closeButton.center = CGPoint(x: (underView.frame.size.width+shutterButton.center.x+shutterButton.frame.size.width/2)/2, y: underView.frame.size.height/2)
        closeButton.addTarget(self, action: #selector(tapedCloseButton(sender:)), for: .touchUpInside)
        underView.addSubview(closeButton)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        // スクリーン設定
        setupDisplay()
        
        // カメラの設定
        setupCamera()
    }
    
    // メモリ解放
    override func viewDidDisappear(_ animated: Bool) {
        // camera stop メモリ解放
        session.stopRunning()
        
        for output in session.outputs {
            session.removeOutput(output as? AVCaptureOutput)
        }
        
        for input in session.inputs {
            session.removeInput(input as? AVCaptureInput)
        }
        
        session = nil
        camera = nil
    }
    
    func setupDisplay(){
        //スクリーンの幅
        let screenWidth = UIScreen.main.bounds.size.width;
        //スクリーンの高さ
        let screenHeight = UIScreen.main.bounds.size.height;
        
        // カメラからの映像を映すimageViewの作成
        if let iv = imageView {
            //以前のimageViewがあれば剥がしておく
            iv.removeFromSuperview()
        }
        imageView = UIImageView()
        imageView.frame = CGRect(x:0.0, y:0.0, width:screenWidth, height:screenHeight)
        view.addSubview(imageView)
        view.sendSubview(toBack: imageView)
    }
    
    func setupCamera(){
        // AVCaptureSession: キャプチャに関する入力と出力の管理
        session = AVCaptureSession()
        
        // sessionPreset: キャプチャ・クオリティの設定
        session.sessionPreset = AVCaptureSessionPresetHigh
        
        // 背面・前面カメラの選択 iOS10での変更
        camera = AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera,
                                               mediaType: AVMediaTypeVideo,
                                               position: .back) // position: .front
        
        // AVCaptureDevice: カメラやマイクなどのデバイスを設定
        /*
        for caputureDevice: AnyObject in AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo){
            //devices()//withDeviceType: AVCaptureDeviceType.builtInWideAngleCamera,
            //mediaType: AVMediaTypeVideo,
            //position: AVCaptureDevicePosition.front
            // 背面カメラを取得
            if caputureDevice.position == AVCaptureDevicePosition.Back {
                camera = caputureDevice as? AVCaptureDevice
            }
        }
        */
        
        // カメラからの入力データ
        do {
            input = try AVCaptureDeviceInput(device: camera) as AVCaptureDeviceInput
        } catch let error as NSError {
            print(error)
        }
        
        // 入力をセッションに追加
        if(session.canAddInput(input)) {
            session.addInput(input)
        }
        
        // AVCaptureVideoDataOutput:動画フレームデータを出力に設定
        output = AVCaptureVideoDataOutput()
        
        // 出力をセッションに追加
        if(session.canAddOutput(output)) {
            session.addOutput(output)
        }
        
        // ピクセルフォーマットを 32bit BGR + A とする
        output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable : Int(kCVPixelFormatType_32BGRA)]
        
        // フレームをキャプチャするためのサブスレッド用のシリアルキューを用意
        output.setSampleBufferDelegate(self, queue: DispatchQueue.main)
        
        output.alwaysDiscardsLateVideoFrames = true
        
        session.startRunning()
        
        // deviceをロックして設定
        do {
            try camera.lockForConfiguration()
            // フレームレート
            camera.activeVideoMinFrameDuration = CMTimeMake(1, 30)
            camera.unlockForConfiguration()
        } catch _ {
        }
    }
    
    
    // 新しいキャプチャの追加で呼ばれる
    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
        
        // キャプチャしたsampleBufferからUIImageを作成
        let image:UIImage = self.captureImage(sampleBuffer: sampleBuffer)
        
        // カメラの画像を画面に表示
        DispatchQueue.main.async() {
            self.imageView.image = image
        }
    }
    
    // sampleBufferからUIImageを作成
    func captureImage(sampleBuffer:CMSampleBuffer) -> UIImage{
        
        // Sampling Bufferから画像を取得
        let imageBuffer:CVImageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        
        // pixel buffer のベースアドレスをロック
        CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0))
        
        let baseAddress:UnsafeMutableRawPointer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)!
        
        let bytesPerRow:Int = CVPixelBufferGetBytesPerRow(imageBuffer)
        let width:Int = CVPixelBufferGetWidth(imageBuffer)
        let height:Int = CVPixelBufferGetHeight(imageBuffer)
        
        // 色空間
        let colorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB()
        
        let newContext:CGContext = CGContext(data: baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace,  bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue|CGBitmapInfo.byteOrder32Little.rawValue)!
        
        let imageRef:CGImage = newContext.makeImage()!
        //let resultImage = UIImage(CGImage: imageRef, scale: 1.0, orientation: UIImageOrientation.Right)
        let resultImage = UIImage(cgImage: imageRef, scale: 1.0, orientation: UIImageOrientation.right)
        return resultImage
    }
    
    
    // タップイベント.
    func tapedShutterButton(sender: UIButton) {
        takeStillPicture()
        
        self.imageView.alpha = 0.4
        
        UIView.animate(withDuration: 0.5, animations: {
            self.imageView.alpha = 1
        })
    }
    
    func takeStillPicture(){
        if var _:AVCaptureConnection? = output.connection(withMediaType: AVMediaTypeVideo){
            // アルバムに追加
            UIImageWriteToSavedPhotosAlbum(self.imageView.image!, self, nil, nil)
        }
    }
    
    func tapedCloseButton(sender: UIButton) {
        print("Close button tapped")
        
        // 前の画面に戻るとき
        // self.dismissViewControllerAnimated(true, completion: nil)
    }
    
    let focusView = UIView()
    
    func tappedScreen(sender: UITapGestureRecognizer) {//tappedScreen(gestureRecognizer: UITapGestureRecognizer)
        let tapCGPoint = sender.location(ofTouch: 0, in: sender.view)
        focusView.frame.size = CGSize(width: 120, height: 120)
        focusView.center = tapCGPoint
        focusView.backgroundColor = UIColor.white.withAlphaComponent(0)
        focusView.layer.borderColor = UIColor.white.cgColor
        focusView.layer.borderWidth = 2
        focusView.alpha = 1
        imageView.addSubview(focusView)
        
        UIView.animate(withDuration: 0.5, animations: {
            self.focusView.frame.size = CGSize(width: 80, height: 80)
            self.focusView.center = tapCGPoint
        }, completion: { Void in
            UIView.animate(withDuration: 0.5, animations: {
                self.focusView.alpha = 0
            })
        })
        
        self.focusWithMode(focusMode: AVCaptureFocusMode.autoFocus, exposeWithMode: AVCaptureExposureMode.autoExpose, atDevicePoint: tapCGPoint, motiorSubjectAreaChange: true)
    }
    
    var oldZoomScale: CGFloat = 1.0
    
    func pinchedGesture(sender: UIPinchGestureRecognizer) {//pinchedGesture(gestureRecgnizer: UIPinchGestureRecognizer)
        do {
            try camera.lockForConfiguration()
            // ズームの最大値
            let maxZoomScale: CGFloat = 6.0
            // ズームの最小値
            let minZoomScale: CGFloat = 1.0
            // 現在のカメラのズーム度
            var currentZoomScale: CGFloat = camera.videoZoomFactor
            // ピンチの度合い
            let pinchZoomScale: CGFloat = sender.scale
            
            // ピンチアウトの時、前回のズームに今回のズーム-1を指定
            // 例: 前回3.0, 今回1.2のとき、currentZoomScale=3.2
            if pinchZoomScale > 1.0 {
                currentZoomScale = oldZoomScale+pinchZoomScale-1
            } else {
                currentZoomScale = oldZoomScale-(1-pinchZoomScale)*oldZoomScale
            }
            
            // 最小値より小さく、最大値より大きくならないようにする
            if currentZoomScale < minZoomScale {
                currentZoomScale = minZoomScale
            }
            else if currentZoomScale > maxZoomScale {
                currentZoomScale = maxZoomScale
            }
            
            // 画面から指が離れたとき、stateがEndedになる。
            if sender.state == .ended {
                oldZoomScale = currentZoomScale
            }
            
            camera.videoZoomFactor = currentZoomScale
            camera.unlockForConfiguration()
        } catch {
            // handle error
            return
        }
    }
    
    func focusWithMode(focusMode : AVCaptureFocusMode, exposeWithMode expusureMode :AVCaptureExposureMode, atDevicePoint point:CGPoint, motiorSubjectAreaChange monitorSubjectAreaChange:Bool) {
        /*
         let concurrentQueue = dispatch_queue_create("com.swift3.imageQueue", DISPATCH_QUEUE_CONCURRENT) //Swift 2 version
         
         let concurrentQueue = DispatchQueue(label:"com.swift3.imageQueue", attributes: .concurrent) //Swift 3 version
         */
        DispatchQueue(label:"com.swift3.imageQueue", attributes: .concurrent).async(execute: {
            //dispatch_queue_create("session queue", DISPATCH_QUEUE_SERIAL)
            let device : AVCaptureDevice = self.input.device
            
            do {
                try device.lockForConfiguration()
                if(device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode)){
                    device.focusPointOfInterest = point
                    device.focusMode = focusMode
                }
                if(device.isExposurePointOfInterestSupported && device.isExposureModeSupported(expusureMode)){
                    device.exposurePointOfInterest = point
                    device.exposureMode = expusureMode
                }
                
                device.isSubjectAreaChangeMonitoringEnabled = monitorSubjectAreaChange
                device.unlockForConfiguration()
                
            } catch let error as NSError {
                print(error.debugDescription)
            }
            
        })
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

こちらの記事もどうぞ