Subclass identification with Swift Generics works with a custom class, but not with a UITapGestureRecognizer

There, I wanted to do something quickly, but I could not figure out how to achieve it, that is, remove gesture recognizers based on the type of class, here is my code (and example), I use swift 2.0 in Xcode 7 beta 5:

I have 3 classes that inherit from UITapGestureRecognizer

class GestureONE: UIGestureRecognizer { /*...*/ } class GestureTWO: UIGestureRecognizer { /*...*/ } class GestureTHREE: UIGestureRecognizer { /*...*/ } 

Add them to the view.

 var gesture1 = GestureONE() var gesture11 = GestureONE() var gesture2 = GestureTWO() var gesture22 = GestureTWO() var gesture222 = GestureTWO() var gesture3 = GestureTHREE() var myView = UIView() myView.addGestureRecognizer(gesture1) myView.addGestureRecognizer(gesture11) myView.addGestureRecognizer(gesture2) myView.addGestureRecognizer(gesture22) myView.addGestureRecognizer(gesture222) myView.addGestureRecognizer(gesture3) 

I am printing an object:

 print(myView.gestureRecognizers!) // playground prints "[<__lldb_expr_224.TapONE: 0x7fab52c20b40; baseClass = UITapGestureRecognizer; state = Possible; view = <UIView 0x7fab52d259c0>>, <__lldb_expr_224.TapONE: 0x7fab52d21250; baseClass = UITapGestureRecognizer; state = Possible; view = <UIView 0x7fab52d259c0>>, <__lldb_expr_224.TapTWO: 0x7fab52d24a60; baseClass = UITapGestureRecognizer; state = Possible; view = <UIView 0x7fab52d259c0>>, <__lldb_expr_224.TapTWO: 0x7fab52c21130; baseClass = UITapGestureRecognizer; state = Possible; view = <UIView 0x7fab52d259c0>>, <__lldb_expr_224.TapTWO: 0x7fab52e13260; baseClass = UITapGestureRecognizer; state = Possible; view = <UIView 0x7fab52d259c0>>, <__lldb_expr_224.TapTHREE: 0x7fab52c21410; baseClass = UITapGestureRecognizer; state = Possible; view = <UIView 0x7fab52d259c0>>]" 

This extension, which I made using a generic function

 extension UIView { func removeGestureRecognizers<T: UIGestureRecognizer>(type: T.Type) { if let gestures = self.gestureRecognizers { for gesture in gestures { if gesture is T { removeGestureRecognizer(gesture) } } } } } 

Then i use it

 myView.gestureRecognizers?.count // Prints 6 myView.removeGestureRecognizers(GestureTWO) myView.gestureRecognizers?.count // Prints 0 

Deletes all gestures D:

And here is an experiment with custom classes

 //** TEST WITH ANIMALS*// class Animal { /*...*/ } class Dog: Animal { /*...*/ } class Cat: Animal { /*...*/ } class Hipo: Animal { /*...*/ } class Zoo { var animals = [Animal]() } var zoo = Zoo() var dog1 = Dog() var cat1 = Cat() var cat2 = Cat() var cat3 = Cat() var hipo1 = Hipo() var hipo2 = Hipo() zoo.animals.append(dog1) zoo.animals.append(cat1) zoo.animals.append(cat2) zoo.animals.append(cat3) zoo.animals.append(hipo1) zoo.animals.append(hipo2) print(zoo.animals) //playground prints "[Dog, Cat, Cat, Cat, Hipo, Hipo]" extension Zoo { func removeAnimalType<T: Animal>(type: T.Type) { for (index, animal) in animals.enumerate() { if animal is T { animals.removeAtIndex(index) } } } } zoo.animals.count // prints 6 zoo.removeAnimalType(Cat) zoo.animals.count // prints 3 

It actually removes the classes that it should: D

What am I missing in UIGestureRecognizer? I ended up with a workaround creating a function that has no generics (boring), such as:

 extension UIView { func removeActionsTapGestureRecognizer() { if let gestures = self.gestureRecognizers { gestures.map({ if $0 is ActionsTapGestureRecognizer { self.removeGestureRecognizer($0) } }) } } } 

It works, of course, but still I would like to have a real solution

I appreciate your help !!

Note: The first question I ask here

+6
source share
1 answer

TL DR:

Use dynamicType to check the execution type of each gesture recognizer against your type parameter.


Great question. It looks like you came across a script when it becomes clear that the difference between Objective-C dynamic typing and Swift static typing becomes clear.

In Swift, SomeType.Type is a type of type metatype that essentially allows you to specify a compile-time type parameter. But this may differ from type at runtime.

 class BaseClass { ... } class SubClass: BaseClass { ... } let object: BaseClass = SubClass() 

In the above example, the object the compilation class is BaseClass , but at runtime it is SubClass . You can check the execution class with dynamicType :

 print(object.dynamicType) // prints "SubClass" 

So why does it matter? As you saw with the Animal test, everything worked out as you expected: your method accepts an argument whose type is the metatype type of the Animal subclass, and then you only remove animals that match that type. The compiler knows that T can be any particular subclass of Animal . But if you specify the Objective-C type ( UIGestureRecognizer ), the compiler will plunge into the indefinite world of dynamic typing Objective-C, and everything will be a little less predictable until runtime.

I have to warn you that I'm a little wooly about the details here ... I do not know the features of how the compiler / runtime handles generics when mixing Swift and Objective-C worlds. Perhaps someone who has some deeper knowledge of the subject can penetrate and find out ...

As a comparison, let's just try a variant of your method, where the compiler can elude the Objective-C world a bit:

 class SwiftGesture: UIGestureRecognizer {} class GestureONE: SwiftGesture {} class GestureTWO: SwiftGesture {} class GestureTHREE: SwiftGesture {} extension UIView { func removeGestureRecognizersOfType<T: SwiftGesture>(type: T.Type) { guard let gestureRecognizers = self.gestureRecognizers else { return } for case let gesture as T in gestureRecognizers { self.removeGestureRecognizer(gesture) } } } myView.removeGestureRecognizers(GestureTWO) 

With the above code, only instances of GestureTWO that we want, at least for Swift types, are deleted. The Swift compiler can view this generic method declaration without touching itself with Objective-C types.

Fortunately, as discussed above, Swift is able to check the type of runtime using dynamicType . Thanks to this knowledge, only a little tweaking is required for your method to work with Objective-C types:

 func removeGestureRecognizersOfType<T: UIGestureRecognizer>(type: T.Type) { guard let gestureRecognizers = self.gestureRecognizers else { return } for case let gesture in gestureRecognizers where gesture.dynamicType == type { self.removeGestureRecognizer(gesture) } } 

The for loop is tied to the gesture recognition signs gesture , the execution type of which is equal to the passed value of the metatype type, so we successfully delete only the specified type of gesture recognizers.

+3
source

All Articles