【Swift】画像のタップした座標を正しく取得する

# 画像のタップした座標を正しく取得する

iOSアプリを開発する中で画像を扱うことは結構あると思いますが、苦戦することもあるかと思います。
自分は画像をタップするとその位置の座標を返してくれる機能を実装しようとしましたが、
画像をタップした際にタップしたはずの場所と実際の座標がずれる問題でハマったので、そのメモを書きます。



# なぜ座標がずれるか
理由は大きく3つありました。

1. 表示されている画像と元の画像のサイズが違うため
2. 一部の画像で表示画像と元の画像の向きが違っていたため（元の画像が右に90°回転）
3. `UIImageView`のサイズが表示されている画像部分より大きくなっているため（余白部分がある）

以下でそれぞれの解決方法を書きます。


## 1. 表示されている画像と元の画像のサイズが違う
実際に表示する画像を縮小して表示したり、拡大したりすると、元の画像と表示されている画像のサイズが違ってしまいます。
また、タップした位置の座標を取得するためには`UIImageView`（表示されている方）から取得する必要がありますが、
画像の色を抽出したい場合などは`UIImage`（元の画像）から座標を取得する必要があります。
そのとき、`UIImageView`と`UIImage`のサイズが違うと座標のズレが生じるので、座標を調整してあげる必要があります。

```swift:SampleViewController.swift

//中略
    //画像用変数
    let image = UIImage(named: "myImage.jpg")!
    var myImageView = UIImageView(image: image)
    //表示されている画像のタップ座標用変数
    var tapPoint = CGPoint(x: 0, y: 0)
    //元の画像のタップ座標用変数
    var originalTapPoint = CGPoint(x: 0, y: 0)
  //中略

    //画像のどこの座標をタップしたかを取得する関数
    func tapAction(sender:UITapGestureRecognizer){
        //ImageView上のタップ座標を取得
        tapPoint = sender.locationInView(myImageView)

        //サイズの倍率を算出し、UIImage上でのタップ座標を求める
        originalTapPoint.x = image.size.width/myImageView.frame.width * tapPoint.x
        originalTapPoint.y = image.size.height/myImageView.frame.height * tapPoint.y       
    }

```


## 2. 一部の画像で表示画像と元の画像の向きが違っていた
iPhoneのカメラで写真を撮影すると、実際に見えている向きと、裏側のデータとしての向きが違うことがあります。
バックカメラ（iPhoneの後ろ側のカメラ）で縦向きに撮影した場合では、裏側では画像の向きが右に90°回転している場合があります。
そのため、タップした場所の座標を取得しようとした時に、x軸とy軸がずれてしまいます。（下図参照）

`表示されている向き`
![画像1.jpg](https://qiita-image-store.s3.amazonaws.com/0/134515/c76a38f7-af86-a150-3707-4d89e08c447a.jpeg)
`裏側のデータ上の向き`
![画像2.jpg](https://qiita-image-store.s3.amazonaws.com/0/134515/9ea48dab-33e9-94a5-5073-afeb2f811251.jpeg)

上図の例（右に90°回転している場合）では表示画像から座標を取得しようとすると、x軸とy軸が逆かつ表示されている画像で言うy軸の向きが逆になります。
なので、画像の向きによって座標の取得の仕方を変えます。（今回は右に90°回転している場合のみ）
なお、画像の向きは`imageOrientation.rawValue`で取得できます。（3のとき右に90°回転）
先ほどの`tapAction(sender:)`を以下のように書き換えます。

```swift:SampleViewController.swift
    
  //中略

    //画像のどこの座標をタップしたかを取得する関数
    func tapAction(sender:UITapGestureRecognizer){

        tapPoint = sender.locationInView(myImageView)
        //向きによって元の画像のTap座標を変える(右に90°回転している)
        switch image.imageOrientation.rawValue {
        case 3:
            originalTapPoint.x = image.size.height/myImageView.frame.height * tapPoint.y
            originalTapPoint.y = image.size.width - (image.size.width/myImageView.frame.width * tapPoint.x)
            
        default:
            originalTapPoint.x = image.size.width/myImageView.frame.width * tapPoint.x
            originalTapPoint.y = image.size.height/myImageView.frame.height * tapPoint.y
        }
    }

```


## 3. `UIImageView`のサイズが表示されている画像部分より大きくなっている
`UIImageView`で`ScaleAspectFit`などを使用すると、画像の縦横比を変えずに`UIImageView`のサイズに合わせることになります。
そのため、表示画像部分の縦か横どちらかが`UIImageView`のサイズと異なってしまう場合があります。
なので、`UIImageView`のサイズを実際に表示されている画像と等しくする必要があります。
画像表示領域を取得するために、`AVMakeRectWithAspectRatioInsideRect`を使用する方法がありましたが、なぜかUIImageViewのサイズをそのまま返してくる場合があったので今回はあえて計算します。

```swift:SampleViewController.swift
    
   
      //中略
        myImageView.frame.size = self.view.frame.size
        
        // 画像の縦横サイズ比率を変えずに制約に合わせる
        myImageView.contentMode = UIViewContentMode.ScaleAspectFit
        
        let imageSize: CGSize
        //ここでも画像の向きが重要です（今回は右に90°の場合のみ）
        switch image.imageOrientation.rawValue {
        case 3:
            imageSize = CGSizeMake(image.size.height, image.size.width)
        default:
            imageSize = CGSizeMake(image.size.width, image.size.height)
           }

        //ImageViewのサイズを表示されている画像のサイズに合わせる
        if imageSize.width > imageSize.height {
            myImageView.frame.size.height = imageSize.height/imageSize.width * myImageView.frame.width
        }else{
            myImageView.frame.size.width = imageSize.width/imageSize.height * myImageView.frame.height
        }

```

## 上記コードの全文

```swift:SampleViewController.swift
    
import UIKit

class SampleViewController: UIViewController {
    
    let image = UIImage(named: "myImage.jpg")!
    var myImageView = UIImageView()
    
    //表示されている画像のタップ座標用変数
    var tapPoint = CGPoint(x: 0, y: 0)
    //元の画像のタップ座標用変数
    var originalTapPoint = CGPoint(x: 0, y: 0)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        myImageView.frame.size = self.view.frame.size
        myImageView.image = image

        // 画像の縦横サイズ比率を変えずに制約に合わせる
        myImageView.contentMode = UIViewContentMode.ScaleAspectFit
        let a = UIImageView(image: image)

        let imageSize: CGSize
        //UIImageの向きによって縦横を変える
        switch image.imageOrientation.rawValue {
            //imageOrientationが3のとき（右に90°回転している）
        case 3:
            imageSize = CGSizeMake(image.size.height, image.size.width)
        default:
            imageSize = CGSizeMake(image.size.width, image.size.height)
           }

        //UIImageViewのサイズを表示されている画像のサイズに合わせる
        if imageSize.width > imageSize.height {
            myImageView.frame.size.height = imageSize.height/imageSize.width * myImageView.frame.width
        }else{
            myImageView.frame.size.width = imageSize.width/imageSize.height * myImageView.frame.height
        }

        //表示位置を真ん中にする
        myImageView.center = self.view.center
        
        //画像のタップイベント有効化
        myImageView.userInteractionEnabled = true
        myImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "tapAction:"))
        
        self.view.addSubview(myImageView)
    }
    
    
    //画像のどこの座標をタップしたかを取得する関数
    func tapAction(sender:UITapGestureRecognizer){
        
        tapPoint = sender.locationInView(myImageView)
        //向きによって元の画像のタップ座標を変える(右に90°回転している場合)
        switch image.imageOrientation.rawValue {
        case 3:
            originalTapPoint.x = image.size.height/myImageView.frame.height * tapPoint.y
            originalTapPoint.y = image.size.width - (image.size.width/myImageView.frame.width * tapPoint.x)
            
        default:
            originalTapPoint.x = image.size.width/myImageView.frame.width * tapPoint.x
            originalTapPoint.y = image.size.height/myImageView.frame.height * tapPoint.y
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

```

以上です。
これで画像のタップした座標を正しく取得できました。
間違いやより良い方法がありましたらご指摘いただけると幸いです。