问题
I have as view, in which I want to perform a swipe right gesture. Unfortunately I receive the error EXC_BAD_ACCESS. Does anybody know what's wrong here ? Please a look at the code below.
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")
}
}
}
回答1:
Finally return true was right. In his answer above he pointed me in the right direction, by suggesting to make Invoker
a subclass of NSObject.
But this was not the only mistake I had in my code. I figured out that when the swipe occurred, the Invoker
object, which was meant to handle the event, had already disappeared from memory. I think the reference to it was not captured by the closure as it should have been. Now I figured out another way of implementing this feature which I would like to share with you:
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()
}
}
And this snippet shows how you can use the code:
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")
}
回答2:
For those who does not like associated objects and other tough stuff I have a small gesture recogniser that does not need any hustle. And it doesn't need to be stored somewhere. Works like a regular GR. Though it has some limitations - it's inherited from UITapGestureRecognizer
and he knows only how to handle taps. But do we often need all the types there are? Personally me not.
final class BindableGestureRecognizer: UITapGestureRecognizer {
private var 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()
}
}
Usage example:
let gestureRecognizer = BindableGestureRecognizer {
print("It's alive!")
}
self.view.addGestureRecognizer(gestureRecognizer)
In case some more meaningful lines are presented, don't forget about [weak self]. We don't want to create those undead buddies.
let colorGestureRecognizer = BindableGestureRecognizer { [weak self] in
self?.view.backgroundColor = .red
}
self.view.addGestureRecognizer(colorGestureRecognizer)
Seems handy for me to purify our view controller from those @objc
one liners and to become a bit more.. reactive?
回答3:
Dude this is my ClosureActionsHandler for UIGestures. I have extended this for UIView, so you can implement on all UIComponents. And this is the Git-link. Happy Coding awesome geeks.
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)
}
}
}
回答4:
Here is a Swift 4.2 method, based on @user1755189 's answer but which I think is a bit more simple to call. First add the following classes:
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)
}
}
Then you can add a UITapGestureRecognizer like this:
addGestureRecognizer(UITapGestureRecognizerWithClosure() {
// do something here
// use $0 to reference the target UIGestureRecognizer
})
回答5:
Here is imho the simpelst solution which doen't need subclassing, so it's quite generic. But it needs associated objects to store the action:
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
objc_setAssociatedObject(self, &Keys.actionKey, 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:)))
}
}
This allows you to use following even with your own subclasses. Usage:
let tapRecognizer = UITapGestureRecognizer { recognizer in
print("Hallo")
}
myView.addGestureRecognizer(tapRecognizer)
来源:https://stackoverflow.com/questions/26223944/uigesturerecognizer-with-closure