web-dev-qa-db-ja.com

Swift 2.2プロトコル拡張コンパイラエラーの#selector

Swift 2.2。

新しい#selectorを使用するように指示する警告が表示されましたが、追加した場合

objective-C Selectorで宣言されたメソッドはありません。

この数行のコードで問題を再現しようとしました。これは簡単にコピーして遊び場にも貼り付けることができます

  protocol Tappable {
    func addTapGestureRecognizer()
    func tapGestureDetected(gesture:UITapGestureRecognizer)
}

extension Tappable where Self: UIView {
    func addTapGestureRecognizer() {
        let gesture = UITapGestureRecognizer(target: self, action:#selector(Tappable.tapGestureDetected(_:)))
        addGestureRecognizer(gesture)
    }
}

class TapView: UIView, Tappable {
    func tapGestureDetected(gesture:UITapGestureRecognizer) {
        print("Tapped")
    }
}

プロトコル@objcのメソッドに追加する提案もありますが、実行する場合は、それを実装するクラスにも追加するように求められますが、クラスを追加するとプロトコルに準拠しませんもう、プロトコル拡張の実装を見ないようだからです。
これを正しく実装するにはどうすればよいですか?

25
Andrea

同様の問題がありました。これが私がしたことです。

  1. プロトコルを@objcとしてマークしました。
  2. デフォルトの動作で拡張したメソッドをオプションとしてマークしました。
  3. その後、セルフを使用しました。 #selectorで。

    @objc public protocol UpdatableUserInterfaceType {
      optional func startUpdateUITimer()
      optional var updateInterval: NSTimeInterval { get }
      func updateUI(notif: NSTimer)
    }
    
    public extension UpdatableUserInterfaceType where Self: ViewController {
    
      var updateUITimer: NSTimer {
        return NSTimer.scheduledTimerWithTimeInterval(updateInterval, target: self, selector: #selector(Self.updateUI(_:)), userInfo: nil, repeats: true)
      }
    
      func startUpdateUITimer() {
        print(updateUITimer)
      }
    
      var updateInterval: NSTimeInterval {
        return 60.0
      }
    }
    
24
someoneAnyone

セレクターであるプロパティを作成できます...例:

protocol Tappable {
    var selector: Selector { get }
    func addTapGestureRecognizer()
}

extension Tappable where Self: UIView {
    func addTapGestureRecognizer() {
        let gesture = UITapGestureRecognizer(target: self, action: selector)
        addGestureRecognizer(gesture)
    }
}

class TapView: UIView, Tappable {
    var selector = #selector(TapView.tapGestureDetected(_:))

    func tapGestureDetected(gesture:UITapGestureRecognizer) {
        print("Tapped")
    }
}

エラーは表示されなくなり、@ objcデコレータでプロトコルとクラスを設定する必要はなくなりました。

このソリューションは最もエレガントではありませんが、今までは問題ありません。

17

この回答はBruno Hecktheuersに非常に似ていますが、「Tappable」プロトコルに準拠したいすべての人に変数「selector」を実装させる代わりに、addTapGestureRecognizer関数にパラメーターとして渡すことを選択します。

protocol Tappable {
    func addTapGestureRecognizer(selector selector: Selector)
    func tapGestureDetected(gesture:UITapGestureRecognizer)
}

extension Tappable where Self: UIView {
    func addTapGestureRecognizer(selector selector: Selector)
        let gesture = UITapGestureRecognizer(target: self, action: selector)
        addGestureRecognizer(gesture)
    }
}

class TapView: UIView, Tappable {    
    func tapGestureDetected(gesture:UITapGestureRecognizer) {
        print("Tapped")
    }
}

そして、セレクターを使用する場所に渡します。

addTapGestureRecognizer(selector: #selector(self.tapGestureDetected(_:)))

この方法により、このプロトコルを実装するものがセレクター変数を実装する必要がなくなり、「@ objc」でこのプロトコルを使用している全員をマークする必要がなくなります。このアプローチは肥大化が少ないように感じます。

7
Peep

Swift 3を使用した作業例を次に示します。これは、コールバック関数を定義するための@objc装飾およびプライベート拡張を必要とせずに標準のSwiftプロトコルを使用します。

protocol PlayButtonPlayable {

    // be sure to call addPlayButtonRecognizer from viewDidLoad or later in the display cycle
    func addPlayButtonRecognizer()
    func handlePlayButton(_ sender: UITapGestureRecognizer)

}

fileprivate extension UIViewController {
    @objc func _handlePlayButton(_ sender: UITapGestureRecognizer) {
        if let playable = self as? PlayButtonPlayable {
            playable.handlePlayButton(sender)
        }
    }
}

fileprivate extension Selector {
    static let playTapped =
        #selector(UIViewController._handlePlayButton(_:))
}

extension PlayButtonPlayable where Self: UIViewController {

    func addPlayButtonRecognizer() {
        let playButtonRecognizer = UITapGestureRecognizer(target: self, action: .playTapped)
        playButtonRecognizer.allowedPressTypes = [ NSNumber(value: UIPressType.playPause.rawValue as Int) ]
        view.addGestureRecognizer(playButtonRecognizer)
    }

}
4
picciano

偶然にも、これをサイドバーで見ました。最近同じ問題が発生しました。残念ながら、Objective-Cランタイムの制限により、プロトコル拡張で@objcを使用できないため、この問題は今年初めに解決されたと思います。

この問題は、プロトコルの適合後に拡張子が追加されるために発生します。そのため、プロトコルへの適合が保証される方法はありません。つまり、NSObjectをサブクラス化し、プロトコルに準拠するものからセレクターとしてメソッドを呼び出すことは可能です。これはほとんどの場合、委任で行われます。

これは、プロトコルに準拠する空のラッパーサブクラスを作成し、ラッパーを使用してラッパーで定義されているプロトコルからそのメソッドを呼び出すことができることを意味します。プロトコルのその他の未定義のメソッドはデリゲートに渡すことができます。 UIViewControllerなどの具象クラスのプライベート拡張を使用し、プロトコルメソッドを呼び出すメソッドを定義する他の同様のソリューションがありますが、これらは特定のクラスに関連付けられており、デフォルトの実装ではありませんプロトコルに準拠する特定のクラス

独自のプロトコル関数のデフォルト実装を実装しようとしていることを認識してください。これは、独自の実装の値を定義するために、独自のプロトコル関数の別を使用します。なんて!

プロトコル:

 public protocol CustomViewDelegate {
     func update()
     func nonDelegatedMethod()
}

表示:

デリゲートを使用し、ラッパーメソッドを定義して、デリゲートのメソッドを安全にアンラップします。

class CustomView: UIView {

    let updateButton: UIButton = {
        let button = UIButton(frame: CGRect(Origin: CGPoint(x: 50, y: 50), size: CGSize(width: 150, height: 50)))
        button.backgroundColor = UIColor.lightGray
        button.addTarget(self, action: #selector(doDelegateMethod), for: .touchUpInside)
        return button
    }()

    var delegate:CustomViewDelegate?

    required init?(coder aDecoder: NSCoder) {
        fatalError("Pew pew, Aghh!")
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(updateButton)
    }

    @objc func doDelegateMethod() {
        if delegate != nil {
           delegate!.update()
        } else {
           print("Gottfried: I wanted to be a brain surgeon, but I had a bad habit of dropping things")
        }
     }


   }

ViewController:

View Controllerをビューのデリゲートに適合させ、プロトコルのメソッドを実装します。

class ViewController: UIViewController, CustomViewDelegate {

    let customView = CustomView(frame: CGRect(Origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200)))

    override func viewDidLoad() {
        super.viewDidLoad()
        customView.backgroundColor = UIColor.red
        customView.delegate = self //if delegate is not set, the app will not crash
        self.view.addSubview(customView)
    }

    // Protocol -> UIView Button Action -> View Controller's Method
    func update() {
        print("Delegating work from View that Conforms to CustomViewDelegate to View Controller")
    }

    //Protocol > View Controller's Required Implementation
    func nonDelegatedMethod() {

       //Do something else 

   }
}

View Controllerはデリゲートに準拠する必要があり、Viewの一部のプロパティのセレクターを設定しなかったことに注意してください。これにより、View ControllerはView(およびそのプロトコル)を分離します。

UIViewとTappableを継承するTapViewという名前のUIViewが既にあるため、実装は次のようになります。

プロトコル:

protocol TappableViewDelegate {
    func tapGestureDetected(gesture:UITapGestureRecognizer)
}

TappableView:

class TappableView: UIView {

    var delegate:TappableViewDelegate?

    required init?(coder aDecoder: NSCoder) {
        fatalError("Pew pew, Aghh!")
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        let gesture = UITapGestureRecognizer(target: self, action: #selector(doDelegateMethod(gesture:)))
        addGestureRecognizer(gesture)
    }

    @objc func doDelegateMethod(gesture:UITapGestureRecognizer) {
        if delegate != nil {
            delegate!.tapGestureDetected(gesture: gesture)
        } else {
            print("Gottfried: I wanted to be a brain surgeon, but I had a bad habit of dropping things")
        }
    }

}

ViewController:

class ViewController: UIViewController, TappableViewDelegate {

    let tapView = TappableView(frame: CGRect(Origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200)))

    override func viewDidLoad() {
        super.viewDidLoad()
        tapView.backgroundColor = UIColor.red
        tapView.delegate = self
        self.view.addSubview(tapView)
    }

    func tapGestureDetected(gesture: UITapGestureRecognizer) {
        print("User did tap")
   }

}
0
RLoniello