PickerController ではまった


iPhone のフォトアルバムから写真を選択して、任意の箇所を切り取って、これを別のビューコントローラに渡すというコードを作りました。
フォトアルバムから写真を選択するときには UIImagePickerController を使いますが、UIImagePickerController には任意の箇所を切り取る編集機能がビルトインされています。しかし、この編集機能では、写真を拡大しないで切り取るとオリジナルのアスペクト比のイメージになったりします。
そこで今回は、オリジナルの写真を持ってきて、この写真の中で正方形のフレームを表示させ、このフレームのサイズをスライダで変え、位置をタップあるいはドラッグで動かして写真の範囲の中で任意のサイズの正方形で切り取るようなルーチンにしました。
以下の例は、この機能のデバッグのために作ったアプリです。
この写真の取り出しと、編集、そして別のビューコントローラに編修済みのイメージを渡すという動作の検証が目的です。
最初のビューコントローラから Move ボタンでイメージピッカーのビューコントローラに遷移し、ここで Pick ボタンで ImagePicker が呼ばれて、選択された写真が画面に表示されて中心部分で写真が収まる最大サイズで黄色いフレームが表示されます。この枠のサイズはスライダーで変化し、位置は画面のタップあるいはドラッグで動くようにしｍした。切取部分が決まったら Done ボタンでそのときの枠の部分のイメージを切り出し、 Return ボタンでこのイメージを最初のビューコントローラに渡して戻るというものです。
これはシミュレータおよび iPhone 6 では全く問題なく動作しますが、iPhone4S の実機を接続すると、時にメモリワーニングが出てクラッシュします。
どうやらイメージを操作しているとメモリが足りなくなってしまうようです。
このプログラムは、UIImagePickerController の allowEditing プロパティに false を設定して、ビルトインの編集機能を使わないようにしています。
そこで、試しにこのビルトインの編集機能を生かして、つまり、
`picker.allowEditing = true` 
として、写真を受け取る　didFinishPickingMediaWithInfo で、
UIImagePickerControllerOriginalImage 
ではなく
UIImagePickerControllerEditedImage
を使うようにします。
そうするとiPhone4S でも問題なく動作するようになりました。
そこで、いろいろな状態で受け取ったイメージのサイズを調べて見ました。

1.　ビルトイン編集を使わないオリジナルイメージ
<dd>(3264.0, 2448.0) @カメラロール
(2048.0, 1536.0)　@フォトストリーム</dd>
2. ビルトイン編集を使った編集後のイメージ
<dd>(640.0, 640.0) </dd>
3. ビルトイン編集を有効にしたオリジナルイメージ
<dd>(1920.0, 1440.0) @カメラロール
	(1920.0, 1440.0) @フォトストリーム </dd>


このように同じoriginalImage を受け取っても allowEditing の状態で受け取るイメージのサイズが変わります。
なお、ここで１，でカメラロールのサイズが 3264 x 2448 になっているのはこの写真が iPhone6 で撮影されたものだからです。
iPhone4S で 8MPixel のオリジナルイメージを使うのは少し厳しいようです。
ということで今回は折角作った編集ルーチンは使わないことにしましたが、受け取ってすぐにサイズを縮小して使うなどの工夫をしてみましょう。

図：ストーリーボード

![150811_XCodeScreen.jpg](https://qiita-image-store.s3.amazonaws.com/0/60473/16461175-7e62-4471-4c48-cad4c4cdd2d8.jpeg "150811_XCodeScreen.jpg")

コード： ChildViewController

```ChildViewController.swift
//
//  AWAChildViewController.swift
//  AWATransferData
//
//  Created by Minori Awamura on 2015/06/07.
//  Copyright (c) 2015年 Minori Awamura. All rights reserved.
//

import UIKit

class AWAChildViewController: UIViewController,
    UINavigationControllerDelegate, UIImagePickerControllerDelegate {
    
    
    @IBOutlet weak var _childText: UITextField!
    
    @IBOutlet weak var _returnButton: UIButton!
    
    @IBOutlet weak var viewForPhoto: UIView!

    @IBOutlet weak var photoFrame: UIImageView!
    
    @IBOutlet weak var cropSize: UISlider!
    
    
    @IBOutlet weak var doneButton: UIToolbar!
    @IBOutlet weak var pickButton: UIBarButtonItem!
    @IBOutlet weak var cancelButton: UIBarButtonItem!
    
    
    var trans = "test"
    var gameImage = UIImage()
    
    var photoImage: UIImage?
    
    var cropFrame: CGContextRef?
    
    var maxSizeOfFrame: CGFloat?
    var minSizeOfFrame: CGFloat?
    
    var heightOfImageInFrame: CGFloat?
    var widthOfImageInFrame: CGFloat?
    
    var scaleOfImage: CGFloat?
    
    var cropFrameView = UIImageView()
    var cropFrameSize: CGFloat = 0.0
    
    var originOfPhotoImage = CGPointZero
    var oppositeOfPhotoImage = CGPointZero
    

    override func viewDidLoad() {
        super.viewDidLoad()
        _childText.text = trans
        
        if let photo = photoImage {
            photoFrame.image = photoImage
        } else {
            photoImage = UIImage(named: "IMG_0281.jpg")
            photoFrame.image = photoImage
        }
        

    }
    
    
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(true)
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    

    

    
// 汎用メソッド
    
    func moveCursorFrame(size: CGFloat, center: CGPoint) {
        if cropFrameView.isDescendantOfView(viewForPhoto) {
            cropFrameView.removeFromSuperview()
        }
        UIGraphicsBeginImageContext(CGSizeMake(size, size))
        cropFrame = UIGraphicsGetCurrentContext()
        CGContextSetLineCap(cropFrame, kCGLineCapSquare)
        CGContextSetLineWidth(cropFrame, 1.5)
        CGContextSetStrokeColorWithColor(cropFrame, UIColor.yellowColor().CGColor)
        CGContextSetFillColorWithColor(cropFrame, UIColor.clearColor().CGColor)
        CGContextMoveToPoint(cropFrame, 0, 0)
        CGContextAddLineToPoint(cropFrame, cropFrameSize, 0)
        CGContextAddLineToPoint(cropFrame, cropFrameSize, cropFrameSize)
        CGContextAddLineToPoint(cropFrame, 0, cropFrameSize)
        CGContextAddLineToPoint(cropFrame, 0, 0)
        
        CGContextStrokePath(cropFrame)
        
        cropFrameView = UIImageView(image: UIImage(CGImage: CGBitmapContextCreateImage(cropFrame)))
        
        cropFrameView.center = center
        
        viewForPhoto.addSubview(cropFrameView)

    }

    @IBAction func doneAction(sender: UIBarButtonItem) {
        var cropFrameOfImage = CGRectZero
        cropFrameOfImage.origin.x = (cropFrameView.frame.origin.x - originOfPhotoImage.x) * scaleOfImage!
        cropFrameOfImage.origin.y = (cropFrameView.frame.origin.y - originOfPhotoImage.y) * scaleOfImage!
        cropFrameOfImage.size.width = cropFrameSize * scaleOfImage!
        cropFrameOfImage.size.height = cropFrameSize * scaleOfImage!
        
        
        UIGraphicsBeginImageContextWithOptions(cropFrameOfImage.size, false, 0)
        photoImage!.drawInRect(CGRectMake(-1 * cropFrameOfImage.origin.x, -1 * cropFrameOfImage.origin.y,
            photoImage!.size.width, photoImage!.size.height))
        gameImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        let cropedImagesize = gameImage.size
        
        photoFrame.image = gameImage
        
        cropFrameView.removeFromSuperview()
    }

    @IBAction func pickAction(sender: UIBarButtonItem) {
        // イメージピッカーの生成
        let picker = UIImagePickerController()
        picker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
        picker.delegate = self
        picker.allowsEditing = false

        
        // ビューコントローラのビューを開く
        presentViewController(picker, animated: true, completion: nil)


    }
    
    @IBAction func cancelAction(sender: UIBarButtonItem) {
    }
  
    @IBAction func sizeChanged(sender: UISlider) {
        let scale = CGFloat(sender.value)
        
        cropFrameSize = maxSizeOfFrame! * CGFloat(cropSize.value)
        var centerOfFrame = cropFrameView.center
        var originOfFrame = CGPointMake(centerOfFrame.x - cropFrameSize / 2, centerOfFrame.y - cropFrameSize / 2)
        var oppositeOfFrame = CGPointMake(originOfFrame.x + cropFrameSize, originOfFrame.y + cropFrameSize)
        
        if originOfFrame.x < originOfPhotoImage.x {
            originOfFrame.x = originOfPhotoImage.x
        } else if oppositeOfFrame.x > oppositeOfPhotoImage.x {
            originOfFrame.x = oppositeOfPhotoImage.x - cropFrameSize
        }
        
        if originOfFrame.y < originOfPhotoImage.y {
            originOfFrame.y = originOfPhotoImage.y
        } else if oppositeOfFrame.y > oppositeOfPhotoImage.y {
            originOfFrame.y = oppositeOfPhotoImage.y - cropFrameSize
        }
        
        centerOfFrame.x = originOfFrame.x + cropFrameSize / 2
        centerOfFrame.y = originOfFrame.y + cropFrameSize / 2
        
        moveCursorFrame(cropFrameSize, center: centerOfFrame)
      // 端にいるときに画像からはみ出さないよう中心位置を変える
    }
    

    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        
        super.touchesBegan(touches, withEvent: event)
        let touch = touches.first as? UITouch
        let pos: CGPoint = touch!.locationInView(photoFrame)
        
        let originOfPhotoFrameX = photoFrame.frame.origin.x     // イメージビュー
        let originOfPhotoFrameY = photoFrame.frame.origin.y
        let imageOriginX = photoFrame.center.x - originOfPhotoFrameX - widthOfImageInFrame! / 2
        let imageOriginY = photoFrame.center.y - originOfPhotoFrameY - heightOfImageInFrame! / 2

        let margin = cropFrameSize / 2.0
        let minCenterX = margin + imageOriginX
        let maxCenterX = imageOriginX + widthOfImageInFrame! - margin
        let minCenterY = imageOriginY + margin
        let maxCenterY = imageOriginY + heightOfImageInFrame! - margin
        
        
        if pos.x >= imageOriginX && pos.x <= (imageOriginX + widthOfImageInFrame!)
            && pos.y >= imageOriginY && pos.y <= (imageOriginY + heightOfImageInFrame!) {
            var centerX, centerY: CGFloat
            if pos.x < minCenterX {
                centerX = minCenterX
            } else if pos.x > maxCenterX {
                centerX = maxCenterX
            } else {
                centerX = pos.x
            }
            if pos.y < minCenterY {
                centerY = minCenterY
            } else if pos.y > maxCenterY {
                centerY = maxCenterY
            } else {
                centerY = pos.y
            }
            cropFrameView.center = CGPointMake(centerX + originOfPhotoFrameX, centerY + originOfPhotoFrameY)
        }
    }
    
    override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
        super.touchesMoved(touches, withEvent: event)
        let touch = touches.first as? UITouch
        let pos: CGPoint = touch!.locationInView(photoFrame)
        
        let originOfPhotoFrameX = photoFrame.frame.origin.x
        let originOfPhotoFrameY = photoFrame.frame.origin.y
        let imageOriginX = photoFrame.center.x - originOfPhotoFrameX - widthOfImageInFrame! / 2
        let imageOriginY = photoFrame.center.y - originOfPhotoFrameY - heightOfImageInFrame! / 2
        
        let margin = cropFrameSize / 2.0
        let minCenterX = margin + imageOriginX
        let maxCenterX = imageOriginX + widthOfImageInFrame! - margin
        let minCenterY = imageOriginY + margin
        let maxCenterY = imageOriginY + heightOfImageInFrame! - margin
        
        
        if pos.x >= imageOriginX && pos.x <= (imageOriginX + widthOfImageInFrame!)
            && pos.y >= imageOriginY && pos.y <= (imageOriginY + heightOfImageInFrame!) {
                var centerX, centerY: CGFloat
                if pos.x < minCenterX {
                    centerX = minCenterX
                } else if pos.x > maxCenterX {
                    centerX = maxCenterX
                } else {
                    centerX = pos.x
                }
                if pos.y < minCenterY {
                    centerY = minCenterY
                } else if pos.y > maxCenterY {
                    centerY = maxCenterY
                } else {
                    centerY = pos.y
                }
                cropFrameView.center = CGPointMake(centerX + originOfPhotoFrameX, centerY + originOfPhotoFrameY)
        }

    }
    
    override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
        super.touchesEnded(touches, withEvent: event)
        let touch = touches.first as? UITouch
        let pos: CGPoint = touch!.locationInView(photoFrame)
        
        let originOfPhotoFrameX = photoFrame.frame.origin.x
        let originOfPhotoFrameY = photoFrame.frame.origin.y
        let imageOriginX = photoFrame.center.x - originOfPhotoFrameX - widthOfImageInFrame! / 2
        let imageOriginY = photoFrame.center.y - originOfPhotoFrameY - heightOfImageInFrame! / 2
        
        let margin = cropFrameSize / 2.0
        let minCenterX = margin + imageOriginX
        let maxCenterX = imageOriginX + widthOfImageInFrame! - margin
        let minCenterY = imageOriginY + margin
        let maxCenterY = imageOriginY + heightOfImageInFrame! - margin
        
        
        if pos.x >= imageOriginX && pos.x <= (imageOriginX + widthOfImageInFrame!)
            && pos.y >= imageOriginY && pos.y <= (imageOriginY + heightOfImageInFrame!) {
                var centerX, centerY: CGFloat
                if pos.x < minCenterX {
                    centerX = minCenterX
                } else if pos.x > maxCenterX {
                    centerX = maxCenterX
                } else {
                    centerX = pos.x
                }
                if pos.y < minCenterY {
                    centerY = minCenterY
                } else if pos.y > maxCenterY {
                    centerY = maxCenterY
                } else {
                    centerY = pos.y
                }
                cropFrameView.center = CGPointMake(centerX + originOfPhotoFrameX, centerY + originOfPhotoFrameY)
        }

    }
    
    
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        let vcP = segue.destinationViewController as! ViewController
        vcP.returnValue = _childText.text
        vcP._photoView1.image = gameImage

        
    }
    
    
    // ビューの戻り
    @IBAction func firstViewReturnActionForSegue(segue: UIStoryboardSegue) {
        
    }


// UIImagePickerControllerDelegate
    
    
    
    func imagePickerController(picker: UIImagePickerController,
        didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
            // イメージの指定
            photoImage = (info[UIImagePickerControllerOriginalImage]! as! UIImage)
            photoFrame.image = photoImage
            
//            photoImage = (info[UIImagePickerControllerEditedImage]! as! UIImage)
 //           photoFrame.image = photoImage
            
            
            // ビューコントローラのビューを閉じる
            picker.presentingViewController?
                .dismissViewControllerAnimated(true , completion: nil)
            
            let photoSize = photoImage!.size
            println(photoSize)
            
            // 矩形フレーム表示
            let widthOfImage = photoImage?.size.width
            let heightOfImage = photoImage?.size.height
            
            let viewWidth = photoFrame.bounds.width
            let viewHeight = photoFrame.bounds.height
            
            let aspectOfImage = heightOfImage! / widthOfImage!
            let aspectOfView = viewHeight / viewWidth
            
            if aspectOfView > aspectOfImage {
                maxSizeOfFrame = viewWidth * aspectOfImage
                heightOfImageInFrame = maxSizeOfFrame
                widthOfImageInFrame = viewWidth
                scaleOfImage = widthOfImage! / widthOfImageInFrame!
            } else {
                maxSizeOfFrame = viewHeight / aspectOfImage
                heightOfImageInFrame = viewHeight
                widthOfImageInFrame = maxSizeOfFrame
                scaleOfImage = heightOfImage! / heightOfImageInFrame!
            }
            minSizeOfFrame = maxSizeOfFrame! * CGFloat(cropSize.minimumValue)
            cropSize.value = 1.0
            cropFrameSize = maxSizeOfFrame! * CGFloat(cropSize.value)
            
            moveCursorFrame(cropFrameSize, center: photoFrame.center)
            
            originOfPhotoImage.x = photoFrame!.center.x - widthOfImageInFrame! / 2
            originOfPhotoImage.y = photoFrame!.center.y - heightOfImageInFrame! / 2
            oppositeOfPhotoImage.x = originOfPhotoImage.x + widthOfImageInFrame!
            oppositeOfPhotoImage.y = originOfPhotoImage.y + heightOfImageInFrame!
            
    }
    
    // イメージピッカーのキャンセル時に呼ばれる
    func imagePickerControllerDidCancel(picker: UIImagePickerController) {
        // ビューコントローラのビューを閉じる
        picker.presentingViewController?
            .dismissViewControllerAnimated(true , completion: nil)
    }

    func saveDefault() {
        let myDefault = NSUserDefaults.standardUserDefaults()
        
        let imageData = UIImagePNGRepresentation(gameImage)
        myDefault.setObject(imageData, forKey: "gamePicture")
        
    }

}
```