带有闭包的UIGestureRecognizer

问题描述 投票:0回答:5

我有一个视图,我想在其中执行向右滑动的手势。不幸的是我收到错误 EXC_BAD_ACCESS。有人知道这里出了什么问题吗?请看下面的代码。

extension UIView {

    func addGestureRecognizerWithAction(nizer: UIGestureRecognizer, action:() -> ()) {

        class Invoker {
            var action:() -> ()
            init(action:() -> ()) {
                self.action = action
            }
            func invokeTarget(nizer: UIGestureRecognizer) {
                self.action()
                println("Hi from invoker")
            }
        }
        addGestureRecognizer(nizer)
        nizer.addTarget(Invoker(action), action: "invokeTarget:")
    }
}

class BugView: UIView {

    override func awakeFromNib() {
        super.awakeFromNib()

        var swipeRight = UISwipeGestureRecognizer()
        swipeRight.direction = UISwipeGestureRecognizerDirection.Right
        self.addGestureRecognizerWithAction(swipeRight) {
            println("Hi from the gesture closure")
        }
    }
}
ios swift uigesturerecognizer
5个回答
43
投票

对于那些不喜欢关联对象和其他困难的东西的人,我有一个不需要任何喧嚣的小型手势识别器。而且它不需要存储在某个地方。与普通 GR 一样工作。尽管它有一些限制 - 它是从

UITapGestureRecognizer
继承的,而且他只知道如何处理水龙头。但我们经常需要所有类型吗?我个人不是。

final class BindableGestureRecognizer: UITapGestureRecognizer {
    private let action: () -> Void

    init(action: @escaping () -> Void) {
        self.action = action
        super.init(target: nil, action: nil)
        self.addTarget(self, action: #selector(execute))
    }

    @objc private func execute() {
        action()
    }
}

使用示例:

    let gestureRecognizer = BindableGestureRecognizer {
        print("It's alive!")
    }
    self.view.addGestureRecognizer(gestureRecognizer)

如果出现一些更有意义的台词,请不要忘记[弱自我]。我们不想创造那些不死伙伴。

    let colorGestureRecognizer = BindableGestureRecognizer { [weak self] in
        self?.view.backgroundColor = .red
    }
    self.view.addGestureRecognizer(colorGestureRecognizer)

对我来说,从那些

@objc
单行代码中纯化我们的视图控制器并变得更加..反应性似乎很方便?


10
投票

这是恕我直言,这是不需要子类化的最简单的解决方案,因此它非常通用。 但它需要关联对象来存储操作:

extension UIGestureRecognizer {

typealias Action = ((UIGestureRecognizer) -> ())

private struct Keys {
    static var actionKey = "ActionKey"
}

private var block: Action? {
    set {
        if let newValue = newValue {
            // Computed properties get stored as associated objects
                 withUnsafePointer(to:  &Keys.actionKey) {
                      objc_setAssociatedObject(self, $0, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
            }
        }
    }
    
    get {
        let action = objc_getAssociatedObject(self, &Keys.actionKey) as? Action
        return action
    }
}

@objc func handleAction(recognizer: UIGestureRecognizer) {
    block?(recognizer)
}

convenience public  init(block: @escaping ((UIGestureRecognizer) -> ())) {
    self.init()
    self.block = block
    self.addTarget(self, action: #selector(handleAction(recognizer:)))
}
}

这允许您甚至对您自己的子类使用以下内容。用途:

    let tapRecognizer = UITapGestureRecognizer { recognizer in
        print("Hallo")
    }
    myView.addGestureRecognizer(tapRecognizer)

编辑:根据 Andrespch 使用“withUnsafePointer”更新了此内容。谢谢你的提示。


7
投票

最后返回true就对了。在上面的回答中,他建议将

Invoker
作为 NSObject 的子类,为我指明了正确的方向。

但这并不是我的代码中唯一的错误。我发现当滑动发生时,用于处理该事件的

Invoker
对象已经从内存中消失了。我认为对它的引用没有被关闭所捕获,因为它应该是这样。现在我想出了另一种实现此功能的方法,我想与您分享:

class UIGestureRecognizerWithClosure: NSObject {  // must subclass NSObject otherwise error: "class does not implement methodSignatureForSelector: -- " !!

    var closure:() -> ()

    init(view:UIView, nizer: UIGestureRecognizer, closure:() -> ()) {
        self.closure = closure
        super.init()
        view.addGestureRecognizer(nizer)
        nizer.addTarget(self, action: "invokeTarget:")
    }

    func invokeTarget(nizer: UIGestureRecognizer) {
        self.closure()
    }
}

此代码片段展示了如何使用该代码:

var swipeRight = UISwipeGestureRecognizer()
swipeRight.direction = UISwipeGestureRecognizerDirection.Right

// swipeWrapper has to be defined as a property: var swipeWrapper:UIGestureRecognizerWithClosure?
// -> this is needed in order to keep the object alive till the swipe event occurs
swipeWrapper = UIGestureRecognizerWithClosure(view: self, nizer:swipeRight) {
    println("Hi from the gesture closure")
}

1
投票

这是一个 Swift 4.2 方法,基于 @user1755189 的答案,但我认为调用起来更简单。首先添加以下类:

class UITapGestureRecognizerWithClosure: UITapGestureRecognizer {
    private var invokeTarget:UIGestureRecognizerInvokeTarget

    init(closure:@escaping (UIGestureRecognizer) -> ()) {
        // we need to make a separate class instance to pass
        // to super.init because self is not available yet
        self.invokeTarget = UIGestureRecognizerInvokeTarget(closure: closure)
        super.init(target: invokeTarget, action: #selector(invokeTarget.invoke(fromTarget:)))
    }
}

// this class defines an object with a known selector
// that we can use to wrap our closure
class UIGestureRecognizerInvokeTarget: NSObject {
    private var closure:(UIGestureRecognizer) -> ()

    init(closure:@escaping (UIGestureRecognizer) -> ()) {
        self.closure = closure
        super.init()
    }

    @objc public func invoke(fromTarget gestureRecognizer: UIGestureRecognizer) {
        self.closure(gestureRecognizer)
    }
}

然后你可以像这样添加一个 UITapGestureRecognizer :

addGestureRecognizer(UITapGestureRecognizerWithClosure() {
    // do something here
    // use $0 to reference the target UIGestureRecognizer  
})

0
投票

老兄,这是我的 UIGestures ClosureActionsHandler。 我已经将其扩展为 UIView,因此您可以在所有 UIComponent 上实现。 这是Git 链接。 快乐编码,很棒的极客。

import Foundation
import UIKit

private var AssociatedObjectHandle: UInt8 = 25
public enum closureActions : Int{
    case none = 0
    case tap = 1
    case swipe_left = 2
    case swipe_right = 3
    case swipe_down = 4
    case swipe_up = 5
}
public struct closure {
    typealias emptyCallback = ()->()
    static var actionDict = [Int:[closureActions : emptyCallback]]()
    static var btnActionDict = [Int:[String: emptyCallback]]()
}

public extension UIView{

var closureId:Int{
    get {
        let value = objc_getAssociatedObject(self, &AssociatedObjectHandle) as? Int ?? Int()
        return value
    }
    set {
        objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}
public func actionHandleBlocks(_ type : closureActions = .none,action:(() -> Void)? = nil) {

    if type == .none{
        return
    }
    var actionDict : [closureActions : closure.emptyCallback]
    if self.closureId == Int(){
        self.closureId = closure.actionDict.count + 1
        closure.actionDict[self.closureId] = [:]
    }
    if action != nil {
        actionDict = closure.actionDict[self.closureId]!
        actionDict[type] = action
        closure.actionDict[self.closureId] = actionDict
    } else {
        let valueForId = closure.actionDict[self.closureId]
        if let exe = valueForId![type]{
            exe()
        }
    }
}

@objc public func triggerTapActionHandleBlocks() {
    self.actionHandleBlocks(.tap)
}
@objc public func triggerSwipeLeftActionHandleBlocks() {
    self.actionHandleBlocks(.swipe_left)
}
@objc public func triggerSwipeRightActionHandleBlocks() {
    self.actionHandleBlocks(.swipe_right)
}
@objc public func triggerSwipeUpActionHandleBlocks() {
    self.actionHandleBlocks(.swipe_up)
}
@objc public func triggerSwipeDownActionHandleBlocks() {
    self.actionHandleBlocks(.swipe_down)
}
public func addTap(Action action:@escaping() -> Void){
    self.actionHandleBlocks(.tap,action:action)
    let gesture = UITapGestureRecognizer()
    gesture.addTarget(self, action: #selector(triggerTapActionHandleBlocks))
    self.isUserInteractionEnabled = true
    self.addGestureRecognizer(gesture)
}
public func addAction(for type: closureActions ,Action action:@escaping() -> Void){

    self.isUserInteractionEnabled = true
    self.actionHandleBlocks(type,action:action)
    switch type{
    case .none:
        return
    case .tap:
        let gesture = UITapGestureRecognizer()
        gesture.addTarget(self, action: #selector(triggerTapActionHandleBlocks))
        self.isUserInteractionEnabled = true
        self.addGestureRecognizer(gesture)
    case .swipe_left:
        let gesture = UISwipeGestureRecognizer()
        gesture.direction = UISwipeGestureRecognizerDirection.left
        gesture.addTarget(self, action: #selector(triggerSwipeLeftActionHandleBlocks))
        self.isUserInteractionEnabled = true
        self.addGestureRecognizer(gesture)
    case .swipe_right:
        let gesture = UISwipeGestureRecognizer()
        gesture.direction = UISwipeGestureRecognizerDirection.right
        gesture.addTarget(self, action: #selector(triggerSwipeRightActionHandleBlocks))
        self.isUserInteractionEnabled = true
        self.addGestureRecognizer(gesture)
    case .swipe_up:
        let gesture = UISwipeGestureRecognizer()
        gesture.direction = UISwipeGestureRecognizerDirection.up
        gesture.addTarget(self, action: #selector(triggerSwipeUpActionHandleBlocks))
        self.isUserInteractionEnabled = true
        self.addGestureRecognizer(gesture)
    case .swipe_down:
        let gesture = UISwipeGestureRecognizer()
        gesture.direction = UISwipeGestureRecognizerDirection.down
        gesture.addTarget(self, action: #selector(triggerSwipeDownActionHandleBlocks))
        self.isUserInteractionEnabled = true
        self.addGestureRecognizer(gesture)
    }


}
}
© www.soinside.com 2019 - 2024. All rights reserved.