Hello again. =)
The class I display as popupContent has 3 graphical elements inside:
UINavigationBar on top of the screen.UIView called videoArea beneath the bar that has width equal to screen width and aspect ratio of 16:9. It is used to display AVPlayerViewController.view contents.UITAbleView taking up the rest of screen space.popupInteractionStyle = .drag to interact with popup.My issue is that default gesture recognizer detects my finger on top of navigation bar and table view, but not the videoArea.
I have overriden the property like so:
override var viewForPopupInteractionGestureRecognizer: UIView {
get {
if videoArea.has(MediaPlayerVC.shared.view) {
return MediaPlayerVC.shared.view
} else {
return view
}
}
}
MediaPlayerVC.shared here is AVPLayerViewController instance I use throughout the app. Now, the intention is to use root view when popupBar is displayed and media player view when popupContent is displayed. The problem is that when I slide my finger on the videoArea, nothing happens.
I have added a KVO inside my tab bar controller, since I use it to display popups:
popupContentView.popupInteractionGestureRecognizer.addObserver(self, forKeyPath: #keyPath(UIPanGestureRecognizer.state), options: [.new], context: nil)
And I print gesture recognizer status into terminal. And gesture recognizer states do change. It changes to began when I touch the screen and then to ended when I remove the finger. So the animation should execute, but for some unknown reason it doesn't.
I'm not sure viewForPopupInteractionGestureRecognizer is the correct approach here.
If you leave the default viewForPopupInteractionGestureRecognizer implementation, the issue is the video does not accept the gesture? It could be a conflict of gesture recognizers.
Please try, instead of overriding the getter of the property, implement the interaction gesture recognizer delegate, and allow for simultaneous interaction.
Have you tried?
yourTargetVC.popupContentView.popupInteractionGestureRecognizer.delegate = yourContentVC
And in YourContentVC:
extension YourContentVC: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
//print("shouldRecognizeSimultaneouslyWith")
return true
}
@LeoNatan No, video does not accept the gesture. Solution by @iDevelopper doesn't help since gestureRecognizer(shouldRecognizeSimultaneouslyWith:) is never invoked.
Is it invoked for other gesture recognizers?
If not, you most likely have not set up the delegate correctly.
@LeoNatan What other gesture recognizers are we talking about? The one that handles table view scrolling, for example?
Delegate is set up correctly since gestureRecognizerShouldBegin(:) and gestureRecognizer(shouldReceive:) are invoked.
For example, it should call shouldRecognizeSimultaneouslyWith: for the table's gesture recognizers.
It does.
Interesting. So it's some shit Apple is pulling behind the scenes. Could you try setting the userInteractionEnabled property to NO for the video view? Just for testing, for now.
Yes, popupInteractionGestureRecognizer works as intended when AVPlayerViewController.view.userInteractionEnabled = false. I shall attempt to get the list of all the gesture recognizers there are inside AVPlayerController.view.subviews.
Hm, I can't get any.
My MediaPlayerVC is a subclass of AVPlayerViewController. I've added a method into it
func gestureRecognizers(for view: UIView) -> [UIGestureRecognizer] {
var recognizers = view.gestureRecognizers ?? [UIGestureRecognizer]()
for subview in view.subviews {
recognizers += gestureRecognizers(for: subview)
}
return recognizers
}
to recursively extract gesture recognizers. I might've screwed up the method though. =)
Anyway, when I invoke gestureRecognizers(for: playerVC.view) inside viewWillAppear(animated:), the array is empty.
It could be that the view does not use gesture recognizers, or it adds them later. Try calling your method a while after viewDidAppear: (using dispatch_after).
Alright, I've overriden MediaPlayerVC.viewDidAppear(:) method like so:
override func viewDidAppear(_ animated: Bool){
super.viewDidAppear(animated)
recognizers = gestureRecognizers(for: self.view)
print(Date.currentTime().toString(), "Gestures recognized:", recognizers.count)
for (i, recognizer) in recognizers.enumerated() {
print(i+1, NSStringFromClass(type(of: recognizer)))
recognizer.delegate = self
}
}
and implemented this method:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.description.range(of:"LNPopupInteractionPanGestureRecognizer") != nil {
print(Date.currentTime().toString(), "LNPopupInteractionPanGestureRecognizer.shouldRequireFailureOf." + NSStringFromClass(type(of: otherGestureRecognizer)))
}
else if otherGestureRecognizer.description.range(of:"LNPopupInteractionPanGestureRecognizer") != nil {
print(Date.currentTime().toString(), NSStringFromClass(type(of: gestureRecognizer)) + ".shouldRequireFailureOf.LNPopupInteractionPanGestureRecognizer")
}
return false
}
First of all, there are 18 gesture recognizers in the hierarchy. =)
Secondly, whenever I slide my finger down, I get a bunch of errors like:
[AVKit] Recognized pan gesture without enabling pan to dismiss.
[AVKit] Unexpected transitioning state.
[AVKit] -[AVTransitionController (0x1c44431b0) gestureTrackerDidCancelTracking:] called with an invalid state 4
There are gesture recognizers that may require our recognizer to fail since stuff like
AVUserInteractionObserverGestureRecognizer.shouldRequireFailureOf.LNPopupInteractionPanGestureRecognizer
is printed a bunch of times.
You will have to find the magic formula to get it it work.
Another option is to iterate all the gesture recognizers and disable all pan gesture recognizers on the player view..
I got it! Inside my AVPlayerViewController subclass I've put the following:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
disableUnnecessaryGestureRecognizers()
}
func disableUnnecessaryGestureRecognizers() {
for recognizer in gestureRecognizers(for: self.view) {
if type(of: recognizer) != UITapGestureRecognizer.self {
recognizer.isEnabled = false
}
}
}
func gestureRecognizers(for view: UIView) -> [UIGestureRecognizer] {
var recognizers = view.gestureRecognizers ?? [UIGestureRecognizer]()
for subview in view.subviews {
recognizers += gestureRecognizers(for: subview)
}
return recognizers
}
It disables all the recognizers except Tap ones. Swiping over video frame works properly now.
馃憤