UITapGestureRecognizer: Modal View is overlapped by another view - swift

I have a UIViewController that I show as a modalPresentationStyle = .overCurrentContext.
This over-the-context view (self.view) has backgroundColor = .clear and a subview called content.
content is full bounds width and half bounds height with a white background.
I've added UITapGestureRecognizer to self.view but I cannot tell to not fire the tap action when tapping the overlapping view (content).
Any idea on how to only trigger the action when the user taps on the view, not on the subview?

You need to conform to UIGestureRecognizerDelegate and then implement shouldReceive touch delegate method:
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let touchView = touch.view {
if touchView.isDescendant(of: view) {
return false
}
}
return true
}
}
So if the touch area is the subView, then the tap gesture will be ignored.

Related

Can't find which view triggered the tap gesture recognizer in my custom view

I know a lot of questions are answered for the issues like this but in my case, the common answers do not work.
I have a custom view which is a small view that appears above the active textfield to show the clipboard data so if the user wants that data in that textField, taps on my custom view so the clipboard text automatically pastes into that textField.
everything works fine when there is a single textField on the page. but if I add another textField, this custom view can't detect the correct textField to paste the text into it.
here is my code inside my customView to detect current textField
private func addTapGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction))
tap.cancelsTouchesInView = true
tap.delegate = self
window?.addGestureRecognizer(tap)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.frame.contains(subviews.first!.frame) {
tapComple?(true)
} else {
tapComple?(false)
}
return true
}
I know that already other answers on the stackoverflow are saing to use this:
if touch.view == self
But it is not working in my case...
The demo project to see everything.
You need to detect if the rectangle contains the point which you touched. You can achieve that with the code below.
Change your gestureRecognizer(_:shouldRecieve:) method to the following:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if subviews.first?.frame.contains(touch.location(in: subviews.first)) == true {
print(true)
tapComple?(true)
} else {
print(false)
tapComple?(false)
}
return true
}
Result:
You don't need to do any calculations or listening gesture recognizer delegate.
Instead of that you can add tap gesture recognizer on your own view, on on the window. So all taps it receive - will be correct ones.
Also right now you're adding your view as subview to text field. This is really a bad practice and will stop this view from receiving taps.
Instead of that I suggest you adding it to to UIViewController view. To do so you need to pass two views into your LContextView: superview and anchorView on which you'll bind your constraints
private func configView(superView: UIView, anchorView: UIView, size: CGSize) {
removePreviusContexes(superView: superView)
superView.addSubview(self)
backgroundColor = .white
translatesAutoresizingMaskIntoConstraints = false
widthAnchor.constraint(equalToConstant: size.width).isActive = true
heightAnchor.constraint(equalToConstant: size.height).isActive = true
centerXAnchor.constraint(equalTo: anchorView.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: anchorView.centerYAnchor, constant: -50).isActive = true
...
private func addTapGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction))
addGestureRecognizer(tap)
}

How to single out and action a UITapGestureRecognizer for a UIView that is in the UIViewController.view that also has a UITapGestureRecognizer?

I have a UIView object that I have added a UITapGestureRecognizer. The main view also has a UITapGestureRecognizer that i use to hide the keyboard. How can i prioritise the tapGesture on the child UIView to do what i need to do? I would like, when tapping the child view to trigger the action associated with this tap, rather than the main views's gesture.
I have tried adding a double tap to the child view, to distinguish between the two gestures, but this just triggers the action for the main views's tap action.
var childView: UIView!
/**Tap screen event listener - to hide the keyboard*/
let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(hideKeyBaord))
self.view.addGestureRecognizer(tapGesture)
let doubleTap = UITapGestureRecognizer.init(target: self, action: #selector(doSomethingElse))
doubleTap.numberOfTouchesRequired = 2
childView.view.addGestureRecognizer(doubleTap)
One way is to implement the shouldReceiveTouch delegate method for tapGesture:
extension YourViewController : UIGestureRecognizerDelegate {
override func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if gestureRecogniser == tapGesture {
// tapGesture should not receive the touch when the touch is inside the child view
let location = touch.location(in: childView)
return !childView.bounds.contains(location)
} else {
return true
}
}
}
And remember to set the delegate of tapGesture to self.

UISlider and Pan Gesture Recognizer do not mix

As asked here: Pan gesture interferes with UISlider and Gesture problem: UISwipeGestureRecognizer + UISlider I cannot get UISlider to work with a pan gesture where in my navigation controller I have a class that has a "back" button capability.
The answers mention that I should add a delegate to my class as such
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let view = touch.view, view == slider {
return false
}
return true
}
However, this method never gets called. I have tried about 15 ways to set the delegate for this class, but I honestly have no clue what to set at the delegate.
let myScreenEdgePanGestureRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action:nil)
myScreenEdgePanGestureRecognizer.delegate = self
Nothing to this effect works. Could someone clearly explain how I can fix this problem. I have tried every approach on the first 2 pages of google. Thank you.
If you see this code more precisely, then code is checking class as a UISlider not object of UISlider.
So in swift you have to write like this
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let view = touch.view
if view is UISlider { // check class as a UISlider
return false
}
return true
}
I didn't run and check my code, if any editing required then do and if you need any help then add comment.
Edit
For UIScreenEdgePanGestureRecognizer, check this post :
https://www.hackingwithswift.com/example-code/uikit/how-to-detect-edge-swipes
Try to declare edges
In Swift it will looks like
let myScreenEdgePanGestureRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action:nil)
myScreenEdgePanGestureRecognizer.delegate = self
myScreenEdgePanGestureRecognizer.edges = .left //.right, .top, .bottom

Add TapGesture to view under tableView

Hello I have a view and inside of this view I have a tableView. What I would like to do is add a tap gesture on the view. My issue is that when I do this, the gesture shadows the tableView default tap gesture and becomes useless. Is there any way to add a gesture to the view underneath without affecting my tableView? Thank you in advance:
Code:
self.mainView.addGestureRecognizer(UITapGestureRecognizer.init(target: self, action: #selector(mainViewTap(_:))))
Add UIGestureRecognizerDelegate to your UIViewController. Then, implement the below delegate function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view is UITableView
}
Now, the UITapGestureRecognizer only affects the UITableView.
Use a button under tableView but above your view. There might be other ways, bit this seems like a quick fix.

Disable swipe to close on AVPlayerController

iOS 11 has introduced a swipe to close AVPlayerController. I have app that is aimed at toddlers so the screen is easily swiped causing the video to close. Is there anyway to remove the gesture to close the player?
I have tried adding a gesture override to the AVPlayerController's view but it doesn't work. There is a possible solution on How can I add Swipe Gesture to AVPlayer in swift 3 but there must be a cleaner way
If AVPlayerController is embedded (not presenting) the Close button is not presented in the controls view.
My solution is to find the subview with gesture recognizers and remove pan gesture recognizer
for v in playerViewController.view.subviews {
if v.gestureRecognizers != nil {
for gr in v.gestureRecognizers! {
if gr is UIPanGestureRecognizer {
// remove pan gesture to prevent closing on pan
v.removeGestureRecognizer(gr)
}
}
}
}
I managed to solve issue. As #Vakas commented, the AVPlayerController shouldn't be subclassed. I had originally subclassed it and presented using a modal segue. This was causing the problem.
To solve it, I created another view controller that embeds the AVPlayerController in it.
import UIKit
import AVKit
class PlayerViewController: UIViewController, AVPlayerViewControllerDelegate {
var videoRecord: Video!
var presentingController = ""
var videos = [Video]()
var presentingPlaylist: Playlist?
let playerViewController = TFLPlayerController()
override func viewDidLoad() {
super.viewDidLoad()
playerViewController.delegate = self
playerViewController.videoRecord = videoRecord
playerViewController.videos = self.videos
playerViewController.allowsPictureInPicturePlayback = false
// Add the original AVPlayerController in here
self.addChildViewController(playerViewController)
let playerView = playerViewController.view
playerView?.frame = self.view.bounds
self.view.addSubview(playerView!)
playerViewController.didMove(toParentViewController: self)
}
}
I basically use this View Controller to pass through the properties such as videos, etc, to the originally subclassed AVPlayerController.
None of the comments above solved the issue (iOS 13+). Solution:
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
avPlayerViewController.view.addGestureRecognizer(panGestureRecognizer)
where handlePanGesture(_:) is the method that will be called if a pan happen on the screen (and the video won't move - that was the problem in the question that it was dragged), and avPlayerViewController is the AVPlayerViewController instance.
Note: if you want to prevent the pinch / rotation and any other gesture, you can add for every gesture a new UI...GestureRecognizer. Just make sure, that all UI...GestureRecognizers' delegate is set, and this function is implemented:
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}