Dismiss UIPopoverPresentationController with any gesture, not just tap - ios

So I have a simple UIPopoverPresentationController that displays some content.
User can dismiss it by tapping anywhere on the screen (default popover behaviour).
I want the popover to be dismissed if the user does any kind of tap or gesture on the screen. Preferably drag gesture.
Any idea if this is possible? And how?

try using touchesBegan:withEvent method
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
if touch.view == self.view {
self.dismiss()
} else {
return
}
}
}

VC is the view presented in the popover.
in the presentViewController:animated:completion: block
[self presentViewController:vc animated:YES completion:^{
UIView *v1 = vc.view.superview.superview.superview;
for (UIView* vx in v1.subviews) {
Class dimmingViewClass = NSClassFromString(#"UIDimmingView");
if ([vx isKindOfClass:[dimmingViewClass class]])
{
UIPanGestureRecognizer* pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(closePopoverOnSwipe)];
[vx addGestureRecognizer:pan];
}
}
}];
you have a UIDimmingView that holds the tap gesture that will close. just add to it. I am using the Class dimmingViewClass = NSClassFromString(#"UIDimmingView"); to avoid making direct use of undocumented APIs. I have not tried yet to send this hack to apple, but will try next week. I hope it will pass. But I tested this and it did call my selector.

I resolved this problem using custom view:
typealias Handler = (() -> Void)?
final class InteractionView: UIView {
var dismissHandler: Handler = nil
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return self
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.dismissHandler?()
}
}
In the viewDidAppear configure this view and add to popover containerView:
fileprivate func configureInteractionView() {
let interactionView = InteractionView(frame: self.view.bounds)
self.popoverPresentationController?.containerView?.addSubview(interactionView)
interactionView.backgroundColor = .clear
interactionView.isUserInteractionEnabled = true
interactionView.dismissHandler = { [weak self] in
self?.hide()
}
}
fileprivate func hide() {
self.dismiss(animated: true, completion: nil)
}

my solution for this problem.
for example if you create a class UIViewController named MyPopoverViewController to present PopViewController.
then in the viewDidLoad() or viewWillAppear(_ animated:) Method add two GestureRecognizer as follows:
protocal MyPopoverControllerDelegate {
func shouldDismissPopover()
}
class MyPopoverViewController : UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// back trace to root view, if it is a UIWindows, add PanGestureRecognizer
// and LongPressGestureRecognizer, to dismiss this PopoverViewController
for c in sequence(first: self.view, next: { $0.superview}) {
if let w = c as? UIWindow {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(dismissPopover(gesture:)))
w.addGestureRecognizer(panGestureRecognizer)
let longTapGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(dismissPopover(gesture:)))
w.addGestureRecognizer(longTapGestureRecognizer)
}
}
#objc private func dismissPopover(gesture: UIGestureRecognizer) {
delegate?.shouldDismissPopover()
}
}
then in your main ViewController, which this PopOverViewController presents, implements the Method of the Protocol.
extension YourMainViewController: MyPopoverControllerDelegate {
func shouldDismissPopover() {
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}

Related

Offset on UIWindow causes the tab bar to show twice when swiping back

I am able to move the system keyboard above the tab bar by moving its window up when keyboardWillShowNotification is sent. However when the view controller is embedded in a navigation controller and I'm slowly swiping back, the tab bar appears twice. Also the offset resets to normal if the swipe is cancelled (obviously since kyeboardWillShow event is not triggered). Observing keyboardWillChangeFrame also doesn't apply.
Here is some sample code to show exactly what I'm doing:
import UIKit
import Foundation
class ViewController: UIViewController { }
class SecondViewController: UIViewController {
let textField = UITextField(frame: CGRect(x: 150, y: 150, width: 150, height: 150))
override func viewDidLoad() {
super.viewDidLoad()
textField.keyboardType = .decimalPad
view.addSubview(textField)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
textField.becomeFirstResponder()
}
}
extension SecondViewController {
private var keyboardOffset: CGFloat {
return -tabBarHeight
}
private var tabBarHeight: CGFloat {
return tabBarController?.tabBar.frame.height ?? 0
}
private var keyboardWindowPredicate: (UIWindow) -> Bool {
return { $0.windowLevel > UIWindow.Level.normal }
}
private var keyboardWindow: UIWindow? {
return UIApplication.shared.windows.last(where: keyboardWindowPredicate)
}
#objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardWindow = keyboardWindow {
keyboardWindow.frame.origin.y = keyboardOffset
}
}
#objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardWindow = keyboardWindow {
keyboardWindow.frame.origin.y = 0
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
textField.resignFirstResponder()
}
}
extension Sequence {
func last(where predicate: (Element) throws -> Bool) rethrows -> Element? {
return try reversed().first(where: predicate)
}
}
Can anyone explain why does this happen and what can I do to prevent this? I know that moving the keyboard is not something usual in iOS, but it was not my decision and I'm trying to work with it.
Maybe the reason is the following:
The docs to touchesBegan(_:with:) say:
UIKit calls this method when a new touch is detected in a view or
window. Many UIKit classes override this method and use it to handle
the corresponding touch events. The default implementation of this
method forwards the message up the responder chain. When creating your
own subclasses, call super to forward any events that you do not
handle yourself. For example, [super touchesBegan:touches
withEvent:event]; If you override this method without calling super (a
common use pattern), you must also override the other methods for
handling touch events, even if your implementations do nothing.
So the 1st thing I would do is to override, as requested by the docs,
the other methods for handling touch requests, even if your
implementations do nothing

touchBegan and TouchEnded overrides are affecting another UIViewController

I have this UIViewController in which i've overrided the touchBegan and touchEnded functions. I also have a button that segueues (push) to another view controller with an SKView on it. But the overrided function in the first controller are still active ei. the calculations done there are still showing on the second ViewController.
Maybe theres something im missing or something im assuming thats wrong. Any help would be appreciated
This is the first view controller
import UIKit
import CoreMotion
class PortraitViewController: UIViewController {
var vc: UIViewController?
var startPoint: CGPoint?
var endPoint: CGPoint?
var movedPoint: CGPoint?
var previousMove = CGPoint(x: 0, y: 0)
var beginTouch: UITouch?
var scaleSum = 0
var isPortrait = true
let DEBUG: Bool = true
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .portrait}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .portrait}
override var shouldAutorotate: Bool {return false}
#IBAction func pinch(_ sender: UIPinchGestureRecognizer) {
if (isPortrait){
let scale = sender.scale
scaleSum += scale.exponent
print(scaleSum)
if(scaleSum > 10) {
scaleSum = 0
print(">10")
}
else if(scaleSum < -10) {
print("<10")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
endPoint = theTouch.location(in: self.view)
let diffx = (endPoint!.x - startPoint!.x)
let diffy = (endPoint!.y - startPoint!.y)
if(diffx != 0 && diffy != 0){
let vector = CGVector(dx: diffx, dy: diffy)
var angle = atan2(vector.dy, vector.dx) * CGFloat(180.0 / M_PI)
if angle < 0 { angle *= -1 } else { angle = 360 - angle }
}
}
}
super.touchesEnded(touches, with: event)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
startPoint = theTouch.location(in: self.view)
}
}
super.touchesBegan(touches, with: event)
}
//One of my attempts to jerry rig a solution
#IBAction func prepToLandscape(_ sender: UIBarButtonItem) {
self.isPortrait = false
print("isPortrait = \(self.isPortrait)")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
This is the second view controller
import UIKit
import CoreMotion
import SpriteKit
class LandScapeViewController: UIViewController {
var vc: UIViewController?
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .landscape}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .landscapeLeft
// MARK:
// MARK: Functions
override func viewDidLoad() {
super.viewDidLoad()
let controllerStoryBoard = UIStoryboard(name: "Main", bundle: nil)
vc = controllerStoryBoard.instantiateViewController(withIdentifier: "Root")
// Part of my attempt to jerry rig a solution
let vcP: PortraitViewController = UIStoryboard(name:"Controller",bundle: nil).instantiateViewController(withIdentifier: "PortraitController") as! PortraitViewController
vcP.isPortrait = false
print("vcP.isPortrait = \(vcP.isPortrait)")
}
override func viewWillAppear(_ animated: Bool) {
self.view.isMultipleTouchEnabled = true
let scene = GameScene(size: joystickView.bounds.size)
scene.backgroundColor = .gray
if let skView = joystickView as? SKView {
skView.showsFPS = false
skView.ignoresSiblingOrder = true
skView.backgroundColor = .red
skView.presentScene(scene)
}
navigationController?.setNavigationBarHidden(true, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func toPortrait(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: {() -> Void in
}
}
Assuming the code shown is everything relevant, this makes sense. What's happening is this: the touch events are hit-tested to the joystickView, but because you haven't implemented a custom touchesBegan(_:with:) on that view, the touch event is passed up to the next UIResponder in the responder chain. That would be the LandScapeViewController. But that class also doesn't implement a custom touchesBegan(_:with:), so the event passes to the next class, which in this case is PortraitViewController. Because PortraitViewController does implement that method, it gets called. There's your confusion.
To fix this, implement the touches… methods on UIResponder for either joystickView or LandScapeViewController, even if they do nothing – but don't call super in them! Note the following, from the touchesBegan(_:with:) documentation:
If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, even if your implementations do nothing.
Where you're overriding touchesBegan(_:with:), you probably don't want to call super. This is because the super implementation is the one that says "oh, shoot, I don't know how to handle this – pass it up the chain!" But when you handle the touch, it should end there, because you're handling it! So only call super when you're not handling the touch – which in your case looks like never, at least for PortraitViewController.
For more information, check out Event Delivery: The Responder Chain.

KeyboardWillHide called while KeyboardWillShow is still animating

Currently I have a view animating with the keyboard when the keyboard shows or hides. I have added a gesture recognizer so that when the user taps off the keyboard it disappears.
The issue I have run into is where if the user taps away to lower the keyboard while the keyboard is appearing, the keyboard disappears and my view is not lowered. I have actually noticed that the view moves even higher for whatever reason.
Here are my keyboard listener methods:
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
if let tabBarController = tabBarController {
responseNode.frame.origin.y -= keyboardSize.height-tabBarController.tabBar.frame.height
tableNode.view.contentInset.bottom += keyboardSize.height-tabBarController.tabBar.frame.height
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
if let tabBarController = tabBarController {
responseNode.frame.origin.y += keyboardSize.height-tabBarController.tabBar.frame.height
tableNode.view.contentInset.bottom -= keyboardSize.height-tabBarController.tabBar.frame.height
}
}
}
and here is how I hide the keyboard:
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
}
func dismissKeyboard() {
view.endEditing(true)
}
}
Any help would be greatly appreciated!
try this
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.view.endEditing(true)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
text.resignFirstResponder()
return true
}

Handle selection of custom CallOutView swift

I've created a custom callOutView, which seem to work fine. However i have problem with handling tap on the custom callOut view. I would like push to a specific viewController when it is selected. How can i achieve this?
below i've added my FBSingleClusterView, which is the custom MKAnnotationView and the bubbleView which is the custom callOutView. beside this i ofcourse have an viewController with a mapView.
FBSingleClusterView Variables
private var hitOutside:Bool = true
var preventDeselection:Bool {
return !hitOutside
}
FBSingleClusterView methods
override func setSelected(selected: Bool, animated: Bool) {
let calloutViewAdded = bubbleView?.superview != nil
if (selected || !selected && hitOutside) {
super.setSelected(selected, animated: animated)
}
self.superview?.bringSubviewToFront(self)
if (bubbleView == nil) {
bubbleView = BubbleView()
}
if (self.selected && !calloutViewAdded) {
bubbleView?.clipsToBounds = true
bubbleView?.layer.masksToBounds = true
self.addSubview(bubbleView!)
let discView = UIImageView()
discView.contentMode = UIViewContentMode.Center
discView.image = UIImage()
discView.image = UIImage(named: "Disclosure")
bubbleView?.contentView.addSubview(discView)
let nameLabel = UILabel()
nameLabel.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
nameLabel.font = UIFont.systemFontOfSize(10)
nameLabel.textColor = UIColor.whiteColor()
nameLabel.text = companyString?.uppercaseString
bubbleView?.addSubview(nameLabel)
discView.snp_makeConstraints { (make) -> Void in
make.top.equalTo(bubbleView!).offset(0)
make.height.equalTo(30)
make.width.equalTo(20)
make.right.equalTo(bubbleView!).offset(0)
}
nameLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(bubbleView!).offset(0)
make.left.equalTo(bubbleView!).offset(10)
make.height.equalTo(30)
make.right.equalTo(bubbleView!).offset(-20)
}
let nameLabelWidth = nameLabel.requiredWidth(companyString!.uppercaseString, font: UIFont.systemFontOfSize(10)) + 35
let bubbleHeight = 35 as CGFloat
bubbleView?.frame = CGRectMake((self.frame.width/2)-(nameLabelWidth/2), -bubbleHeight-2, nameLabelWidth, bubbleHeight)
}
if (!self.selected) {
bubbleView?.removeFromSuperview()
}
}
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
var hitView = super.hitTest(point, withEvent: event)
if let callout = bubbleView {
if (hitView == nil && self.selected) {
hitView = callout.hitTest(point, withEvent: event)
}
}
hitOutside = hitView == nil
return hitView;
}
bubbleView methods
override public func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let viewPoint = superview?.convertPoint(point, toView: self) ?? point
// let isInsideView = pointInside(viewPoint, withEvent: event)
let view = super.hitTest(viewPoint, withEvent: event)
return view
}
override public func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
return CGRectContainsPoint(bounds, point)
}
}
Have you tried using instantiateViewControllerWithIdentifier? You can trigger this when tapping a button, in the viewDidLoad or in whatever other method you want it to trigger and push a new a viewController.
Firstly, set the "Storyboard ID" for the viewController in your storyboard. Like this:
Example:
Here's an example of how you can push to a new viewController when tapping a cell. As mentioned you can use this method in different functions to push to another viewController :
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let yourNewView = self.storyboard!.instantiateViewControllerWithIdentifier("yourStoryBoardID") as! ViewControllerYourePushingTo
self.presentViewController(yourNewView, animated: true, completion: nil)
}
In your case this is in the hitTest func.
I asked a similar question not too long ago. Here's the reference: Swift: Triggering TableViewCell to lead to a link in a UIWebView in another ViewController
Here's the link from Apple Dev: InstantiateViewControllerWithIdentifier
You can add gesture on custom annotation view in didselectannotation and navigate to other view controllor from gesture action

Hide controls in AVPlayerViewController -- only at start

If you set AVPlayerViewController.showsPlaybackControls to false, the controls will not show at all. Even if you tap the screen.
I want the controls to start out hidden, but still be able to summon them by tapping. If I set the mentioned property to true, they start out visible. (Yes they fade after a few seconds.) Is there a way to start hidden, but still be accessible?
UPDATE: I ended up making my own controls for better customization. It's more difficult but worth the time. Please read Apple's sample code for reference. It's about implementing PiP but also about making custom controls: https://developer.apple.com/library/prerelease/ios/samplecode/AVFoundationPiPPlayer/Introduction/Intro.html
UPDATE: When tapped, AVPlayerViewController only fires touchesBegan event, and not touchesEnded event. But it's enough to show the controls.
First you need to hide the control. Put this code right before you present AVPlayerViewController
YourAVPlayerViewController.showsPlaybackControls = false
Then subclass AVPlayerViewController and add this function:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.showsPlaybackControls = true
super.touchesBegan(touches, withEvent: event)
}
OLD SOLLUTION:
I've just solved this. The main idea is to put a UIView on top of the AVPlayerViewController to reveive tap gesture, and hide that UIView when it is no longer needed.
Here's the code:
import AVKit
import UIKit
// Create a custom AVPlayerViewController
#available(iOS 8.0, *)
final class CustomAVPlayerViewController: AVPlayerViewController {
// Create a UIView to put on top of all
lazy var topView = UIView(frame: CGRectMake(0, 0, width, height))
override func viewDidLoad() {
super.viewDidLoad()
// For sure, set it to clearcolor
// (DON'T set alpha = 0 because it will stop receiving user interaction)
topView.backgroundColor = UIColor.clearColor()
// Add it to the view of AVPlayerViewController
self.view.addSubview(topView)
// Bring it to front
self.view.bringSubviewToFront(topView)
// Add a tap gesture recognizer
topView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap"))
}
// Handle the tap
func handleTap() {
// Show the control
self.showsPlaybackControls = true
// Hide the topView. You can unhide it when needed later.
self.topView.hidden = true
}
}
And when you need to hide the controls, do this:
var AVViewController = CustomAVPlayerViewController()
...
// Hide controls
AVViewController.showsPlaybackControls = false
// Show topView
AVViewController.topView.hidden = false
I think I've solved this using dynamic gesture recognizer relationships. The solution avoids custom controls (for consistency), uses only public API and does not subclass AVPlayerViewController (which is explicitly disallowed, as noted in other answers).
Here's how:
Make a container view controller that embeds AVPlayerViewController. (This is useful regardless of the controls, because you need to put the playback logic somewhere.)
Set showsPlaybackControls to false initially.
Add a UITapGestureRecognizer to recognize the initial tap.
In the action method for the gesture recognizer, set showsPlaybackControls to true.
So far, it would work, but the controls would disappear immediately on that initial tap. To fix that, set yourself as a delegate for the gesture recognizer, implement gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer: and return true for any other single-tap gesture recognizer.
Here's the actual implementation in Swift; check andreyvit/ModalMoviePlayerViewController repo for the latest code:
import UIKit
import AVKit
import AVFoundation
public class ModalMoviePlayerViewController: UIViewController {
private let fileName: String
private let loop: Bool
private var item: AVPlayerItem!
private var player: AVPlayer!
internal private(set) var playerVC: AVPlayerViewController!
private var waitingToAutostart = true
public init(fileName: String, loop: Bool = true) {
self.fileName = fileName
self.loop = loop
super.init(nibName: nil, bundle: nil)
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
let url = NSBundle.mainBundle().URLForResource(fileName, withExtension: nil)!
item = AVPlayerItem(URL: url)
player = AVPlayer(playerItem: item)
player.actionAtItemEnd = .None
player.addObserver(self, forKeyPath: "status", options: [], context: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ModalMoviePlayerViewController.didPlayToEndTime), name: AVPlayerItemDidPlayToEndTimeNotification, object: item)
playerVC = AVPlayerViewController()
playerVC.player = player
playerVC.videoGravity = AVLayerVideoGravityResizeAspectFill
playerVC.showsPlaybackControls = false
let playerView = playerVC.view
addChildViewController(playerVC)
view.addSubview(playerView)
playerView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
playerView.frame = view.bounds
playerVC.didMoveToParentViewController(self)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ModalMoviePlayerViewController.handleTap))
tapGesture.delegate = self
view.addGestureRecognizer(tapGesture)
}
deinit {
player.pause()
player.removeObserver(self, forKeyPath: "status")
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func togglePlayPause() {
if isPlaying {
pause()
} else {
play()
}
}
func restart() {
seekToStart()
play()
}
func play() {
if player.status == .ReadyToPlay {
player.play()
} else {
waitingToAutostart = true
}
}
func pause() {
player.pause()
waitingToAutostart = false
}
var isPlaying: Bool {
return (player.rate > 1 - 1e-6) || waitingToAutostart
}
private func performStateTransitions() {
if waitingToAutostart && player.status == .ReadyToPlay {
player.play()
}
}
public override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
performStateTransitions()
}
#objc func didPlayToEndTime() {
if isPlaying && loop {
seekToStart()
}
}
private func seekToStart() {
player.seekToTime(CMTimeMake(0, 10))
}
public override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if !playerVC.showsPlaybackControls {
playerVC.showsPlaybackControls = true
}
super.touchesBegan(touches, withEvent: event)
}
}
extension ModalMoviePlayerViewController: UIGestureRecognizerDelegate {
#IBAction func handleTap(sender: UIGestureRecognizer) {
if !playerVC.showsPlaybackControls {
playerVC.showsPlaybackControls = true
}
}
/// Prevents delivery of touch gestures to AVPlayerViewController's gesture recognizer,
/// which would cause controls to hide immediately after being shown.
///
/// `-[AVPlayerViewController _handleSingleTapGesture] goes like this:
///
/// if self._showsPlaybackControlsView() {
/// _hidePlaybackControlsViewIfPossibleUntilFurtherUserInteraction()
/// } else {
/// _showPlaybackControlsViewIfNeededAndHideIfPossibleAfterDelayIfPlaying()
/// }
public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if !playerVC.showsPlaybackControls {
// print("\nshouldBeRequiredToFailByGestureRecognizer? \(otherGestureRecognizer)")
if let tapGesture = otherGestureRecognizer as? UITapGestureRecognizer {
if tapGesture.numberOfTouchesRequired == 1 {
return true
}
}
}
return false
}
}
thegathering's answer is good. I would override touchesCancelled instead so that the controls do not show and then hide again.
override public func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
// toggle the player controls on if they were set to off
if !self.showsPlaybackControls {
self.showsPlaybackControls = true
}
}
A simple way to do it in Swift 3 is to set myController.showsPlaybackControls = false, and to overlay the whole player view with a button or gesture recognizer. I embed it into another view in another controller on a storyboard to make this simple and to not override the player controller. The trick then is to hide the button after being clicked once, because the player controller will thereafter track taps to show/hide the controls.
#IBAction func enableControls(button:UIButton)
{
controller?.showsPlaybackControls = true
button.isHidden = true //The button is only needed once, then the player takes over.
}

Resources