Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

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

なぜ座標がずれるか

理由は大きく3つありました。

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

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

1. 表示されている画像と元の画像のサイズが違う

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

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
裏側のデータ上の向き
画像2.jpg

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

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のサイズが表示されている画像部分より大きくなっている

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

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
        }

上記コードの全文

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()
    }
}

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

yoshd
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away