I have a collection view cell and a button as its subview. If you click the cell it goes to the detail page. And if you click the button it adds the item to the basket. I need to block the button from clicking more than 1. So I am disabling the button for a few seconds. But this time if I click the button before the delay ends, it goes to the detail page from the button too. Is there a way to solve this without disabling the cell itself?
Use custom delegate as call back in cell that will let the ViewController know that button is disable when user will tap on button. Store that disable state in some store property of ViewController.
let say flag = false
After that when user will tap cell didSelect delegate will get trigger. Then add there a check if flag == false do nothing and vice versa.
After few seconds change the stat of flag i.e. flag = true.
In this way you will not need to disable the cell and you can perform other events there.
Just giving you an idea as I cannot see you code I hope this will help.
This is my app with one bookmark action implemented with leadingSwipeActionsConfigurationForRowAt.
What I need is to make the app editable through two-finger pan gesture when this bookmark action is visible, same as iOS 13's Mail App.
As far as I know, the delegate method tableView(_:shouldBeginMultipleSelectionInteractionAt:) is needed to acheive such interaction. didBeginMultipleSelectionInteractionAt may have some use.
But strangely, shouldBeginMultipleSelectionInteractionAt is not fired when swipe action is visible. And swipe action also set tableView.isEditing to true. So when the bookmark action is visible, the tableView's isEditing is true.
Based on my testing on iOS 13, while didBeginMultipleSelectionInteractionAt is fired when swipe action is visible, it's also fired everytime a row is selected or deselected. So I think it's hard to toggle editting mode here.
I think there are should be some easy ways to do it as Mail App already have this interaction. What am I missing here?
I am using an imported button object, Floaty (https://github.com/kciter/Floaty) which is a subclass of the UIView class. In one of the view controllers where I am using this button (which includes a Text Field), the button fails to be selected when tapped if I include the following line to close the keyboard when tapped away from...
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard (_:)))
self.view.addGestureRecognizer(tapGesture)
The idea for how to close the keyboard came from this SO query: iOS - Dismiss keyboard when touching outside of UITextField
I thought part of the issue could be that the Floaty button needs to be in the front, so I added the following line of code, found from this SO query: Losing tap recognition after adding a UIScrollView under a UIButton
floaty.bringSubviewToFront(self.view)
I added this line to the end of my ViewDidLoad() function in the view where it is used.
(Important maybe to note is that all of the aforementioned code is in the ViewDidLoad function for my view controller)
Is there another way to handle closing a keyboard when tapping away from a text field? Or is there something else that I should do to the Floaty (UIView) object to handle tap / touch events?
You can try use Hit Testing. With hit testing, you can assign a view that handles touch. This great article explains how to apply hit testing http://smnh.me/hit-testing-in-ios/
I have a use case where I'm adding a UITapGestureRecogniser to a UITableViewCell subclass which already has existing behaviour for navigating to another screen when tapped. The issue is that the tap seems to be 'captured' by the recogniser and then not sent on afterwards - the code in my custom recogniser is called, but tapping the cell no longer navigates to a different screen. Preliminary research suggests that if a recogniser handles a gesture then it isn't sent any further along - is it possible to change this? I have researched the responder chain but to no avail.
Cheers
For the benefit of anyone else who might happen upon this same issue in the future, I have solved my problem using this answer. All I needed to do was the following:
let recogniser = UITapGestureRecognizer(target: self, action: #selector(clickHandler))
recogniser.cancelsTouchesInView = false
User puts her finger on the screen. This triggers a UITouchEvent, phase Began, which calls the touchesBegan:withEvent: method in controllerA, which performs a segue from controllerA to controllerB.
User lifts her finger off the screen. This triggers a UITouchEvent, phase Ended, which calls some callback method.
Question: What and where is this callback method? It's not in controllerA, and it's not in controllerB. From what I can tell, it's not in any view. But it exists.
To clarify, here's what's going on (according to #switz):
In response to -touchesBegan:withEvent:, a view controller is presented
modally via a segue
When the user lifts up their finger, the view controller should be dismissed.
The question is how to react to the finger being lifted, since
-touchesEnded:withEvent: is not invoked.
The short answer is the presented view controller needs to use the "Over Full
Screen" modalPresentationStyle instead of the default "Full Screen" style
(this can either be specified as the presentation style of the segue, or if
that's "Default" then the presentation style of the presented view controller).
The long answer requires a brief overview of how touch handling works. This
explanation ignores gesture recognizers:
When a touch begins, it's delivered to the "topmost" view that contains the
touch point. From there it gets passed along the responder chain until some
object decides to handle the touch (which is signified by implementing
-touchesBegan:withEvent: and not calling super).
Subsequent changes to the touch (e.g. moved, ended, canceled) are delivered back
to the same view that accepted the touch. The view will continue to receive the
touch events until the touch finishes or cancels.
A touch is canceled either when the application moves to the background (because
e.g. a phone call came in), or when a UIKit class like UIScrollView decides
that it needs to take over touch handling (because the finger moved far enough
that it looks like the user wants to scroll). There's also some funny stuff here
with UIScrollView.delaysContentTouches, but that can be ignored.
But there's a wrinkle, something that isn't documented: touch delivery only
happens so long as the view remains associated with the window. If the view that
is considered "topmost" (the view that is associated with the UITouch) is
removed from the window, then the touch is considered to have vanished and,
importantly, no events for that touch are delivered again, to anyone. This is
true even if the view in question is not the object handling touches.
And that final wrinkle is the cause for this problem. Because the default
"Full Screen" presentation style actually removes the old view controller's
view from the window, the touch handling immediately stops. However, the "Over
Full Screen" presentation style does not remove it, it merely covers up the old
view with the one. "Over Full Screen" is typically used when the presented view
controller is not fully opaque, but in this case we're using it so touch
handling isn't interrupted.
But that's not all. There's another problem here, which is when the view that's
being touched lives inside a UIScrollView (one that either is scrollable or
always bounces). In that case, even with "Over Full Screen", you'll find that,
while the touch events continue to be delivered, moving your finger around a bit
suddenly causes the touch to be canceled. This is because the UIScrollView
doesn't know it's covered up and has decided that the user is actually trying to
scroll. This causes it to cancel the touch.
There is a solution to this, though. It's kind of ugly, but the solution is to
immediately cancel any scrolling on any enclosing scroll view when performing
the segue. This can be done with code like the following:
class ViewController: UIViewController {
// this is called from -touchesBegan:withEvent: from a child view
// the child view is `sender`
func touchDown(sender: UIView) {
var view = sender.superview
while view != nil {
if let scrollView = view as? UIScrollView {
// toggle the panGestureRecognizer enabled state to immediately
// cause it to fail.
let enabled = scrollView.panGestureRecognizer.enabled
scrollView.panGestureRecognizer.enabled = true
scrollView.panGestureRecognizer.enabled = enabled
}
view = view?.superview
}
performSegueWithIdentifier(identifier, sender: self)
}
// ...
}
Of course, no discussion of touch handling would be complete without gesture
recognizers. Gesture recognizers change pretty much everything about touch
handling. They get first dibs on any touches, and they can interrupt view touch
handling at any time. For example, the UIScrollView's UIPanGestureRecognizer
is what is used for scrolling, and when it moves into the "began" state (because
the user has moved their finger enough), that's what causes the touch to be
canceled.
So given this, really the best solution here is to not implement
-touchesBegan:withEvent: at all, but to use a gesture recognizer. The easiest
solution here is to use a UILongPressGestureRecognizer with
minimumPressDuration set to 0 and allowableMovement set to some
ridiculously high value (since you don't want movement to cancel the touch). I'm
recommending this because UILongPressGestureRecognizer is a continuous
recognizer, meaning it will send events for Began, Moved, and Ended, and with
the recommended settings, it will send them in response to the touch beginning,
moving, and ending. What's more, once your recognizer starts handling the touch,
this automatically prevents any other recognizers (such as the scroll view's pan
recognizer) from "taking over" and canceling the touch.
Note that if you're attaching your gesture recognizer to the scrollView itself
(e.g. a UITableView) but only want to respond to touches in certain locations
(such as on a row), then you'll need to restrict the recognizer. You can use the
delegate method gestureRecognizer(_:shouldReceiveTouch:) to do this, something
like this:
func gestureRecognizer(recognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
// if you might be the delegate of multiple recognizers, check for that
// here. This code will assume `recognizer` is the correct recognizer.
// We're also assuming, for the purposes of this code, that we're a
// UITableViewController and want to only capture touches on rows in the
// first section.
let touchLocation = touch.locationInView(self.tableView)
if let indexPath = self.tableView.indexPathForRowAtPoint(touchLocation) {
if indexPath.section == 0 {
// we're on one of the special rows
return true
}
}
return false
}
This way the recognizer won't prevent the tableView's panGestureRecognizer
from scrolling when the user touches elsewhere on the table.