68

I have written Swift code that attempts to remove all gesture recognizers from all subviews of a given custom UIView type.

let mySubviews = self.subviews.filter() {
   $0.isKindOfClass(CustomSubview)
}
for subview in mySubviews {
   for recognizer in subview.gestureRecognizers {
      subview.removeGestureRecognizer(recognizer)
   }
}

But the for recognizer line produces the compiler error:

'[AnyObject]?' does not have a member named 'Generator'

I have tried changing the for recognizer loop to for recognizer in enumerate(subview.gestureRecognizers), but that produces the compiler error:

Type '[AnyObject]?!' Does not conform to protocol 'SequenceType'

I see that UIView's gestureRecognizers method returns [AnyObject]??, and I think that the doubly wrapped return values are tripping me up. Can anyone help me?

UPDATE: Revised, compiling code is:

if let recognizers = subview.gestureRecognizers {
   for recognizer in recognizers! {
      subview.removeGestureRecognizer(recognizer as UIGestureRecognizer)
   }
}
Brian McCafferty
  • 683
  • 1
  • 5
  • 6

3 Answers3

127

UPDATE FOR iOS 11

In general it is (and has always been) a bad idea to remove all gesture recognizes from a view by looping through its gestureRecognizers array. You should only remove gesture recognizers that you add to the view, by keeping track of those recognizers in your own instance variable.

This takes on new importance in iOS 11 for views that are involved in drag and drop, because UIKit adds its own gesture recognizers to those views to recognize drags and drops.

UPDATE

You no longer need to cast to UIGestureRecognizer, because UIView.gestureRecognizers was changed to type [UIGestureRecognizer]? in iOS 9.0.

Also, by using the nil-coalescing operator ??, you can avoid the if statement.

for recognizer in subview.gestureRecognizers ?? [] {
    subview.removeGestureRecognizer(recognizer)
}

However, the shortest way to do it is this:

subview.gestureRecognizers?.forEach(subview.removeGestureRecognizer)

We can also do the filtering of the subviews in a for loop like this:

for subview in subviews where subview is CustomSubview {
    for recognizer in subview.gestureRecognizers ?? [] {
        subview.removeGestureRecognizer(recognizer)
    }
}

Or we can wrap it all up into one expression (wrapped for clarity):

subviews.lazy.filter { $0 is CustomSubview }
    .flatMap { $0.gestureRecognizers ?? [] }
    .forEach { $0.view?.removeGestureRecognizer($0) }

The use of .lazy should prevent it from creating unnecessary temporary arrays.

ORIGINAL

This is one of those annoying things about Swift. Your for loop would just work in Objective-C, but in Swift you have to explicitly unwrap the optional array:

if let recognizers = subview.gestureRecognizers {
    for recognizer in recognizers {
        subview.removeGestureRecognizer(recognizer as! UIGestureRecognizer)
    }
}

You could force-unwrap it (for recognizer in subview.gestureRecognizers!), but I'm not sure whether gestureRecognizers can return nil and you'll get a runtime error if it does and you force-unwrap it.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thank you! I've got it compiling now with your help! I had to make 2 changes to your code to get it to compile: 1) the for loop needs `recognizers` to be force-unwrapped and 2) the `recognizer` argument must be cast to UIGestureRecognizer, since UIView's gestureRecognizers method returns an array of AnyObject. I'll edit my question to include the final code. – Brian McCafferty Oct 05 '14 at 21:42
  • 1
    You will also need to cast it: `subview.removeGestureRecognizer(gesture as UIGestureRecognizer)` – Aggressor Dec 04 '14 at 21:54
  • 1
    The cast is obsolete in iOS 9.0. – rob mayoff Dec 06 '15 at 16:20
48

Simplest solution

yourView.gestureRecognizers?.removeAll()
user3144836
  • 4,060
  • 3
  • 27
  • 27
  • 1
    Or `view.gestureRecognizers = []`. – rob mayoff Jun 18 '17 at 16:53
  • It is the best solution. Remove individual gestures (example: cell?.removeGestureRecognizer(swipeLeft) ) not work for me. – Markus Jul 12 '18 at 23:26
  • 2
    This is wrong, To remove a gesture recognizer from a view is more than just releasing it (as you do in your code). You also have to detach the recognizer from the view: https://developer.apple.com/documentation/uikit/uiview/1622413-removegesturerecognizer – rommex Dec 02 '18 at 09:21
10

Simpler way to do that is

for subview in self.subviews as [UIView] {
    if subview.isKindOfClass(CustomSubview) {
        subview.gestureRecognizers?.removeAll(keepCapacity: false)
    }
}
Ah Ryun Moon
  • 370
  • 5
  • 4