协议扩展编译器错误中的Swift 2.2 #selector

时间:2016-03-23 17:30:11

标签: ios swift protocols

我有一个协议扩展,它曾经在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中附加到该方法,但是如果我这样做也要求我将它添加到实现它的类中,但是一旦我添加了类就不符合到协议了,因为它似乎没有看到协议扩展中的实现。
我该如何正确实现?

5 个答案:

答案 0 :(得分:24)

我有类似的问题。这就是我所做的。

  1. 将协议标记为@objc。
  2. 将我使用默认行为扩展的任何方法标记为可选。
  3. 然后使用Self。在#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
      }
    }
    

答案 1 :(得分:16)

您可以创建一个属性作为选择器...示例:

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装饰器设置协议和类。

这个解决方案不是最优雅的,但直到现在看起来还不错。

答案 2 :(得分:7)

这个答案与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”将此协议标记为每个人。像这种方法的感觉不那么臃肿。

答案 3 :(得分:4)

这是一个使用Swift 3的工作示例。它使用标准的Swift协议,无需任何@objc装饰和私有扩展来定义回调函数。

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 :(得分:0)

我碰巧在侧栏看到这个,我最近遇到了同样的问题..不幸的是,由于Objective-C运行时限制你不能在协议扩展上使用@objc,我相信这个问题在今年年初就已经关闭了。 / p>

问题出现是因为在协议一致性之后添加了扩展,因此无法保证满足协议的一致性。也就是说,有可能从一个子类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:

使视图控制器符合视图的委托:并实现协议的方法。

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 

   }
}

请注意,视图控制器只需要符合委托,并且没有设置视图的某些属性的选择器,这会将视图(及其协议)与视图控制器分开。

您已经拥有一个名为TapView的UIView,它继承自UIView和Tappable,因此您的实现可能是:

协定:

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

}