33529


UIPanGestureRecognizerの利用方法について

Swift3入門勉強中です。  
imageRounderクラスでイメージの表示を行い、UIPanGestureRecognizerを利用してPanジェスチャー用のdoPanを呼び出そうとしています。  
しかし、実行時にPanジェスチャーを行うと下記エラーが発生します。

[code]

    2017-03-20 15:46:52.478 imageView01[30745:1710752] -[UIView doPan]: unrecognized selector sent to instance 
    
[/code]

多分、Swiftの基本が分かっていないレベルですが、どなたかアドバイスをいただけると助かります。

* * *
[code]

    //ViewController.swift
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let imageRun = imageRounder(imageName:"pic01",myView:view)
            }
    
[/code]

* * *
[code]

    //imageRounder.swift
    
      import UIKit
    
    let imageRect = UIImageView()
    
    class imageRounder{
        var imgName:String   
        var rootView:UIView  
    
        let imageRect = UIImageView()
    
        init(imageName:String,myView:UIView){
            imgName=imageName
            rootView = myView
    
            imageRect.isUserInteractionEnabled = true
    
            let panGesture = UIPanGestureRecognizer(target: rootView, action: Selector(("doPan")))
            imageRect.addGestureRecognizer(panGesture)
    
        　　rootView.addSubview(imageRect)
           }
    
        func doPan(sender: UIPanGestureRecognizer) {
            let move:CGPoint = sender.translation(in: rootView)
            sender.view!.center.x += move.x
            sender.view!.center.y += move.y
            sender.setTranslation(CGPoint(x:0 , y:0), in:rootView)
        }
    
[/code]


CC BY-SA 3.0
2017-03-25T08:06:42.647
33525
2018-09-10T04:02:13.357
2017-03-25T10:05:20.503
21142
question
ios swift3
UIPanGestureRecognizerの利用方法について


少なくともこの行に修正が必要です。

[code]

    let panGesture = UIPanGestureRecognizer(target: rootView,
                         action: Selector(("doPan")))
    
[/code]

`Selector(("doPan"))`と言う書き方はSwiftコンパイラーが特別な理由がある場合用に用意している記法で、「なんらかの理由で文字列リテラルの形でセレクタを記載しないといけないが、そのせいで出る警告を抑止したい時に使う」ものです。

通常のコードを書く場合には、 **セレクタを記載する必要のある場所では必ず`#selector`構文を使う**
と覚えておいた方が良いでしょう。(Swiftコンパイラーの示してくるサジェスチョンを意味もよくわからずに受け入れてはいけない、と言うのも覚えておいた方が良いかもしれません。)

また、action-targetパターンでセレクタを指定したら、 **ターゲットはそのセレクタを実装しているクラスのインスタンスでないといけません** 。

上記の行を以下のように置き換えて見てください:

[code]

    let panGesture = UIPanGestureRecognizer(target: self,
                         action: #selector(doPan(sender:)))
    
[/code]

この変更により、`#selector`構文が簡単なエラーチェックをおこなってくれるので、別の問題が見つかるかもしれません:

[code]

    func doPan(sender: UIPanGestureRecognizer) {
    
[/code]

↓

[code]

    @objc func doPan(sender: UIPanGestureRecognizer) {
    
[/code]

* * *

以上の修正で、とりあえずコンパイルが通るはずですが、あなたがご質問に掲載のコード、動作確認ができる状態ではないようです。少し書き換えた上で動作確認させていただきました。

[code]

    import UIKit
    
    class ImageRounder {
        var imgName:String
        var rootView:UIView
    
        //###`UIImageView()`なんて未定義のイニシャライザを使うべきではない        
        let imageRect: UIImageView
    
        init(imageName:String,myView:UIView){
            imgName=imageName
            rootView = myView
            //###imageRectを適切に初期化する
            imageRect = UIImageView(image: UIImage(named: imageName))
    
            imageRect.isUserInteractionEnabled = true
    
            let panGesture = UIPanGestureRecognizer(target: self, action: #selector(doPan(sender:)))
            imageRect.addGestureRecognizer(panGesture)
    
            rootView.addSubview(imageRect)
        }
    
        @objc func doPan(sender: UIPanGestureRecognizer) {
            let move:CGPoint = sender.translation(in: rootView)
            sender.view!.center.x += move.x
            sender.view!.center.y += move.y
            sender.setTranslation(CGPoint(x:0 , y:0), in:rootView)
        }
    }
    
[/code]

  * Swiftでクラス名が小文字始まりは気持ち悪いので書き換え
  * グローバルの方の`let imageRect = UIImageView()`は不要なので削除
  * プロパティの方の`let imageRect = UIImageView()`は不適切な初期化なので書き換え  
(パラメータなしのイニシャライザ`UIImageView()`は動作未定義なので使うべきでない。)

この状態で実行したところ、エラーにこそならないものの、`doPan(sender:)`メソッドが呼ばれていないようです。どうやら、`ViewController`クラスにも修正が必要なようです。

[code]

    import UIKit
    
    class ViewController: UIViewController {
    
        var imageRun: ImageRounder! //###<-ImageRounderのインスタンスはずっと保持されていないといけない
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            imageRun = ImageRounder(imageName:"pic01", myView:view)
        }
    }
    
[/code]

Target-actionパターンの **targetインスタンスは、どこか別の場所で強参照を保持する必要があります**
。(`UIPanGestureRecognizer.init(target:action:)`の`target:`にしただけでは強参照は保持されません。)従って、`imageRun`がローカル変数のままだと`viewDidLoad()`終了時に解放されてしまうので、`ViewController`のインスタンスプロパティに変更しました。

以上の修正で、こちらの動作確認ではちゃんと`doPan(sender:)`メソッドが呼ばれるようになったのですが、あなたの元コードが動作確認に使えるものにはなっていないので、これがあなたの期待する動作なのかどうかはわかりません。何かお気付きの点があれば、ご質問内容を編集するなどして、問題点をおしらせください。


CC BY-SA 3.0
2017-03-25T09:12:03.600
33527
2017-03-25T09:12:03.600
13972
33525
answer


すでについている回答の、補足的な説明をします。  
`UIPanGestureRecognizer`の、メソッド`setTranslation(_:in:)`と、`translation(in:)`の使い方についてです。

[code]

    import UIKit
    
    class ViewController: UIViewController {
    
        var imageView: UIImageView?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            imageView = UIImageView(image: UIImage(named: "pic01"))
            if let imageView = imageView {
                imageView.center = CGPoint(x: 100.0, y: 100.0)
                imageView.isUserInteractionEnabled = true
                let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ViewController.panGesture(_:)))
                imageView.addGestureRecognizer(panGestureRecognizer)
                view.addSubview(imageView)
            }
        }
    
        func panGesture(_ recognizer: UIPanGestureRecognizer) {
            if let imageView = imageView {
                if recognizer.state == .began {
                    recognizer.setTranslation(imageView.center, in: view)
                } else if recognizer.state == .changed {
                    imageView.center = recognizer.translation(in: view)
                }
            }
        }
    
    }
    
[/code]

別クラスを作って云々というところをバッサリはしょって、書きました。`UIPanGestureRecognizer`のセレクタ`panGesture(_:)`だけ見てください。  
`UIPanGestureRecognizer`だけでなく、`UIGestureRecognizer`すべてで言えることですが、プロパティ`state`を使って、
**開始** （`.began`）、 **継続中** （`.changed`）、 **終了** （`.ended`）、 **処理キャンセル**
（`.cancelled`）の条件分岐を行い、それぞれの処理を記述します。ここでは、必要なのは`.began`と`.changed`だけなので、この二つだけ実装します。  
`.began`で、メソッド`setTranslation(_:in:)`を使い、`imageView`の位置（`center`）をセットします。  
`.changed`で、メソッド`translation(in:)`を使うと、`setTranslation(_:in:)`でセットした座標に、移動量をプラスした座標が返ってきます。`imageView`の位置をセットしたのですから、返り値は、そのまま`imageView`の移動先の座標となります。  
ちょっとわかりにくい説明かもしれませんが、じっさいにこのコードを動かしてみれば、一目瞭然で理解いただけると思います。


CC BY-SA 3.0
2017-03-25T11:01:27.433
33529
2017-03-25T11:01:27.433
18540
33525
answer