Good day, I have UIImageView inside UIView and I want to handle event, when user tap on UIView background, but not on UIImageView. How can I achieve it? I read about UITapGestureRecognizer, but it confuse me a little.
P.S. If u can plz answer in swift.
If you want to disable UITapGestureRecognizer on the child view of your main view then you have to enable User Interaction of your UIImageView like this
imgTest.userInteractionEnabled = true;
then you have to set the Delegate of your UITapGestureRecognizer to detect the UITouch of the view, set the delegate like this
let touch = (UITapGestureRecognizer(target: self, action: "tapDetected:"))
touch.delegate = self
self.view.addGestureRecognizer(touch)
Then you have to implement the Delegate method of UITapGestureRecognizer in which you will detect the view which is currently touched like this
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.valueForKey("view")!.isKindOfClass(UIImageView) {
return false
} else {
return true
}
}
In the above code i have disable the tap gesture on UIImageView
Now, this method will work only for your self.view not for UIImageView
func tapDetected(sender: UITapGestureRecognizer) {
self.dismissViewControllerAnimated(false, completion: nil)
}
Add this line for your imageView because UIImageView default user interaction is disable. If you will enable UIImageView userinteraction then it will not work for your UIView and outside of UIImageView it will work
yourImage.userInteractionEnabled = YES;
Try this your tap clicked will work
Related
I have a problem where I have a UIScrollView and a Header (UIView) inside my main View and my Header is over my UIScrollView as such:
UIView.
|
|- UIScrollView.
|
|- Header. (UIView)
I want my header to be able to detect taps on it, but I also want my scroll view to be able to scroll when I drag over my Header which right now it is not possible because my Header is over it and is blocking the scroll.
To sum up, I want my Header to detect taps but forward scrolls to my UIScrollView.
To tackle this problem I tried multiple things, and here are some of them:
Adding a UIPanGestureRecognizer to my Header so it is able to detect dragging
Adding a UITapGestureRecognizer to my Header so it is able to detect tapping
Setting isUserInteractionEnabled = false when dragging begins so the gesture can be passed to the next UIResponder which in this case is my UIScrollView
Setting isUserInteractionEnabled = true once my dragging has finished so it can again detect tapping
This is the code snippet:
override func viewLoad() {
myScreenEdgePanGestureRecognizer = UIPanGestureRecognizer(target: self, action:#selector(handlePan))
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(handleTap(_:)))
headerView.addGestureRecognizer(myScreenEdgePanGestureRecognizer)
headerView.addGestureRecognizer(tapGestureRecognizer)
}
#objc func handlePan(_ sender: UITapGestureRecognizer){
print("dragging")
if headerView.isUserInteractionEnabled{
headerView.isUserInteractionEnabled = false
}
if sender.state == .began {
} else if sender.state == .ended {
headerView.isUserInteractionEnabled = true
}
}
#objc func handleTap(_ sender: UITapGestureRecognizer){
print("tapped")
}
At this point I see how dragging and tapping are being detected just fine, but for some reason isUserInteractionEnabled = false seems to not be changing how the view is behaving. This code is acting as isUserInteractionEnabled is always true no mater what.
Things that I have also tried besides this:
overriding the hitTest function inside UIButton
overriding touchesBegan, touchesMoved, touchesEnded methods overriding next
setting the variable to return ScrollView as the next UIResponder
setting the isExclusiveTouch method in UIButton
changing the isUserInteractionEnabled in every way possible
I was struggling with this problem too, you should try to use methods of UIGestureRecognizerDelegate which allows you to handle simultaneous gestures.
Connect your gesture recognizers delegates e.g. tapGestureRecognizer.delegate = self
Make your ViewController conform this protocol e.g.
extension YourViewController: UIGestureRecognizerDelegate {}
Implement this function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true }
I want to remove UIView from screen after user tap something except that view. (to visualize it for you I will upload sketch of my view)
And I want to remove blue UIView after user tap on something except buttons in this view. What should I use?
EDIT:
In blue UIView are two buttons and I want to remove that view when user tap on background image
I did what #yerpy told me to do but it isn't working.
func test(gestureRecognizer: UITapGestureRecognizer) {
print("test")
}
func setUpBackgroundImageView() {
self.view.addSubview(backgroundImageView)
backgroundImageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
backgroundImageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
backgroundImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
backgroundImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let tap = UITapGestureRecognizer(target: self, action: #selector(test(gestureRecognizer:)))
backgroundImageView.addGestureRecognizer(tap)
tap.delegate = self
}
And I also add shouldReceiveTouch function to UIGestureRecognizerDelegate. What am I doing wrong?
Add UIGestureRecognizer to the super view :
As you said you have image view as a background.
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped(gestureRecognizer:)))
imageView.addGestureRecognizer(tapRecognizer)
tapRecognizer.delegate = self
Adding target action :
func tapped(gestureRecognizer: UITapGestureRecognizer) {
// Remove the blue view.
}
And then inside UITapGestureRecognizerDelegate :
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.superview!.superclass! .isSubclass(of: UIButton.self) {
return false
}
return true
}
}
Hope it helps !
EDIT
Make sure that user can touch on the view by enabling : self.view.userInteractionEnabled = true
1- Add a view below your view, let call it overlay (gray one)
2- Add your container view with all your buttons inside (green one)
3- Add a tap gesture to the overlay (drag tap to overlay view)
4- Create a #IBAction of the tap to the viewcontroller
5- Write code to hide your green view inside the #IBAction
Image
Image
you can use UITapGestureRecognizer for that view
override func viewDidLoad() {
super.viewDidLoad()
// Add "tap" press gesture recognizer
let tap = UITapGestureRecognizer(target: self, action: #selector(tapHandler))
tap.numberOfTapsRequired = 1
backgroundimage.addGestureRecognizer(tap)
}
// called by gesture recognizer
func tapHandler(gesture: UITapGestureRecognizer) {
// handle touch down and touch up events separately
if gesture.state == .began {
} else if gesture.state == .ended {
}
}
You can
subclass with that background view, and implement an inherited method -beganTouches:(there is another para but I forget what it is)
Add a UITapGestureRecognizer to that view
Add a giant button that covers the entire screen under all the buttons. Anytime the user presses on the giant button underneath the smaller buttons, do what you want it to do.
Not the most elegant but it works :P
I placed a small UIView inside a UITableViewCell.
In case of a tapping on such UIView, I would like to open a popup.
In case of a tapping outside such UIView, I would like to perform what it's defined in the UITableView "didSelectRowAtIndexPath" function.
What it's happening at the moment is that when I click on the view both things happen: the popup is opened and the "didSelectRowAtIndexPath" function is trigged.
How can I make sure that when I click on that UIView the "didSelectRowAtIndexPath" function is not triggered?
current implementation:
I defined a UITapGestureRecognizer for the UIView inside my custom UITableViewCell class.
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(MyCustomTableViewCell.clickOnHelp(_:)))
myClickAreaUIView.addGestureRecognizer(tapGesture)
The best solution I suggest is to change the UIView to a UIButton and handle the touch with a touchUpInside on the button.
This way you will reach your objective, as UIButton automatically prevents touch event forwarding to superview
Try setting the inner view's exclusiveTouch property to true.
cell.tapView.exclusiveTouch = true
You should use this method from UIGestureRecognizerDelegate
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
Create a customClass for your UIView lets say CustomView of type UIView
Connect your UIView's #IBOutlet conforming to tableViewCell of type CustomView
In CustomView class call this function
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
super.pointInside(point, withEvent: event)
return CGRectContainsPoint(self.bounds, point)
}
This will tell you whether or not the location at which user tapped was contained inside that UIView bounds or not, if it returns true do pop up in your UIGestureRecognizer functions and follow https://stackoverflow.com/a/18606840/6297658 , if false do the opposite
I suggest to implement the following method of UIGestureRecognizerDelegate like below
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
print("in shouldReceiveTouch")
gestureRecognizer.delegate = self
if ([...]){ // touch location is in the UIView
print("touching UIView")
return false // prevent from forwarding touch to superview
} else {
println("touching elsewhere")
return true
}
}
just write this code inside cellForRowAtIndexPath() for did select prevent::
cell.selectionStyle = UITableViewCellSelectionStyle.None
I am a beginner in Swift, and am trying to add a swipe gesture recognizer to my UIView. I have inserted a gradient CALayer to index 0 to have a gradient background.
My problem is:
Swipe gestures for right and left work fine, but for Down it doesn't work, why?
Set the delegate of swipe gestures that you are adding to the view.
let swipeGesture = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipe:"))
swipeGesture.delegate = self
self.view.addGestureRecognizer(swipeGesture)
self.mySwipeGesture = swipeGesture
GestureRecognizerDelegate asks if two gesture recognizers should be allowed to recognize gestures simultaneously. Return true to allow both gestureRecognizer and otherGestureRecognizer to recognize their gestures simultaneously. The default implementation returns falseāno two gestures can be recognized simultaneously. Implement the following delegate to achieve this.
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
//Identify gesture recognizer and return true else false.
return gestureRecognizer.isEqual(self.mySwipeGesture) ? true : false
}
}
Swiping Up & Down are the default property of table view. I would suggest you to disable the scrolling of the table view whenever you want to do something on the overlay.
tableView.scrollEnabled = NO;
If you are performing dragging of a particular cell then long press on it and then start dragging.
This is how you can achieve this.
Hope this helps.
I've got a tableView inside of a pageViewController and when swiping on a cell to bring up the option to delete the cell the gesture is only recognized under certain circumstances, say you swiped very quickly and aggressively.
I imagine this is happening because it's not sure whether the swiping gesture is meant for the pageView or the tableView. Is there a way to specifically determine where the swipe gesture is happening to enable a nice smooth display of the delete button?
Theory:
Both UIPageViewController and UITableView are implemented using UIScrollView, where UIPageViewController embeds UIScrollView and UITableView is a subclass of UIScrollView
UITableView also uses a couple of UIPanGestureRecognizers to bring in all the magic. One of these is UISwipeActionPanGestureRecognizer which handles the swipe to delete actions.
This issue is caused because UIPageViewControllers UIPanGestureRecognizer wins in the conflict with the UITableViews UISwipeActionPanGestureRecognizers.
So we have to some how tell UIPageViewController to ignore gestures if UITableViews UISwipeActionPanGestureRecognizer are in action.
Luckily there is something already provided by UIGestureRecognizer.
UIGestureRecognizer's require(toFail otherGestureRecognizer: UIGestureRecognizer) creates a relation between the two gesture recognizers that will prevent the gesture's actions being called until the other gesture recognizer fails.
So all we had to do is fail UIPageViewControllers embedded UIScrollviews panGestureRecognizer when UITableViews UISwipeActionPanGestureRecognizer are triggered.
There are two ways you could achieve this.
Solution 1: Add a new gesture recognizer to table view and Mimic UISwipeActionPanGestureRecognizer. And make UIPageViewController panGesture require to fail this new gestureRecognizer
Solution 2 (A bit dirty): Make a string comparision to the UITableView's UISwipeActionPanGestureRecognizer and make UIPageViewController panGesture require to fail this new gestureRecognizer
Code:
Solution 1
A helpful utility to get UIPageViewControllers embedded UIScrollView
extension UIPageViewController {
var scrollView: UIScrollView? {
return view.subviews.first { $0 is UIScrollView } as? UIScrollView
}
}
Add the below code to UIViewController holding the UITableView and call it from viewDidLoad()
func handleSwipeDelete() {
if let pageController = parent?.parent as? UIPageViewController {
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: nil)
gestureRecognizer.delaysTouchesBegan = true
gestureRecognizer.cancelsTouchesInView = false
gestureRecognizer.delegate = self
tableView.addGestureRecognizer(gestureRecognizer)
pageController.scrollView?.canCancelContentTouches = false
pageController.scrollView?.panGestureRecognizer.require(toFail: gestureRecognizer)
}
}
And finally delegate methods
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let panGesture = gestureRecognizer as? UIPanGestureRecognizer else {
return false
}
let translation = panGesture.translation(in: tableView)
// In my case I have only trailing actions, so I used below condition.
return translation.x < 0
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer.view == tableView
}
Solution 2 (A bit dirty)
A helpful utility to get UIPageViewControllers embedded UIScrollView
extension UIPageViewController {
var scrollView: UIScrollView? {
return view.subviews.first { $0 is UIScrollView } as? UIScrollView
}
}
Add the below code to UIViewController holding the UITableView and call it from viewDidLoad()
func handleSwipeDelete() {
guard let pageController = parent as? UIPageViewController else {
return
}
pageController.scrollView?.canCancelContentTouches = false
tableView.gestureRecognizers?.forEach { recognizer in
let name = String(describing: type(of: recognizer))
guard name == "_UISwipeActionPanGestureRecognizer" else {
return
}
pageController.scrollView?
.panGestureRecognizer
.require(toFail: recognizer)
}
}
I had the same problem. I found a solution that works well.
Put this in your UIPageViewController's viewDidLoad func.
if let myView = view?.subviews.first as? UIScrollView {
myView.canCancelContentTouches = false
}
PageViewControllers have an auto-generated subview that handles the gestures. We can prevent these subviews from cancelling content touches. The tableview will be able to capture swipes for the delete button, while still interpreting swipes that fail the tableview's gesture requirements as page swipes. The delete button will show in cases where you hold and swipe or swipe "aggressively."
You can set delaysContentTouches to false on the tableView itself as well. This solution worked for my collection view's UISlider elements.
See Swift 4.0 code below:
yourTableView.delaysContentTouches = false
I have found a working solution by reassigning UIScrollView's panGestureRecognizer's delegate to my class and ignoring the original delegate when a right-to-left pan is detected. I used method swizzling for that.
Please check my sample project: https://github.com/kambala-decapitator/SwipeToDeleteInsidePageVC
In case you have a tableView in the first or the last viewController inside of your UIPageViewController maybe think of disabling the bouncing at the end of your UIPageViewController first and then implement this solution.
In case you don't know how to disable the bounce of the UIPageViewController, check out my answer here.