I'm trying to override the touchesShouldCancel method for a subclass of a scrollview.
This is my subclass code:
class MyScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
print("Works")
return false
}
}
I have made a scrollview of this class and it scrolls it just doesn't ever print "works". I would like to add some functionality to this but it's never being called. According to the apple documentation:
'The scroll view calls this method just after it starts sending tracking messages to the content view.'
This makes me believe the method should be called whenever I am scrolling and I can't figure out why it's not. Cheers if anyone has any ideas.
From the docs:
Because a scroll view has no scroll bars, it must know whether a touch signals an intent to scroll versus an intent to track a subview in the content. To make this determination, it temporarily intercepts a touch-down event by starting a timer and, before the timer fires, seeing if the touching finger makes any movement. If the timer fires without a significant change in position, the scroll view sends tracking events to the touched subview of the content view. If the user then drags their finger far enough before the timer elapses, the scroll view cancels any tracking in the subview and performs the scrolling itself. Subclasses can override the touchesShouldBegin(_:with:in:), isPagingEnabled, and touchesShouldCancel(in:) methods (which are called by the scroll view) to affect how the scroll view handles scrolling gestures.
Do this experiment.
Add a subview to your scrollview that intercepts touches (think UIButton, not UILabel).
Make sure your content is actually scrollable
Press down on the button and wait for just a moment, then start sliding your finger.
When you tap down on the scroll view and start dragging right away, it assumes you want to scroll the scroll view and not interact with the button.
However when you do that slight pause, it wants to determine if you're trying to interact with the scroll view or with the button.
touchesShouldCancel will then fire.
Related
So I recently implemented a collection view in my app, and I got a bug that I can't seem to solve, searched it and saw no threads about it.
If I have my cursor/finger over the cells i can't scroll through my collection view i need select a "empty" area to scroll.
Second strange Behavior I came across is that I can't directly touch a cell. I need some sort of swipe gesture over it to trigger the code when a cell is selected.
If I go to my collection view on my storyboard and select Delays Content Touches and Cancellable Content Touches in the scrollview section, the collection view scrolls just fine but if I put my finger/cursor over a cell with these option enabled I can't access any cells anymore.
This completely confuses me.
and thank you for reading/considering this thread.
Let's see what your two properties do.
delaysContentTouches: If the value of this property is true, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is false , the scroll view immediately calls touchesShouldBegin(_:with:in:). The default value is true.
canCancelContentTouches: If the value of this property is true and a view in the content has begun tracking a finger touching it, and if the user drags the finger enough to initiate a scroll, the view receives a touchesCancelled(_:with:) message and the scroll view handles the touch as a scroll. If the value of this property is false, the scroll view does not scroll regardless of finger movement once the content view starts tracking.
First, you set delaysContentTouches to false. So the scrollview immediately calls the content view's touch handling methods, allowing it to handle the touch. Obviously, the scroll view won't start scrolling right away because of this, even if you drag.
Second, you also set canCancelContentTouches to false. But if the scroll view isn't allowed to "take over" touches that the content already handles (by cancelling them), it is never able to start scrolling later on either. So if your touch hits a content view, there is no possible way for the scroll view to start scrolling: it isn't allowed to scroll right away because it isn't allowed to delay the content touches, and it can't start scrolling later because it can't cancel the content touches.
I don't know what happens within your cells, not sure what code you put in there. However, you should probably allow your tableview to both delay touches (that means that your cell won't handle swipes that are cancelled immediately anyway because they were intended to be scroll gestures), and to cancel content touches (that means that when you touch down and don't release, you can still start a scroll gesture after a cell became highlighted).
i had the same problem when touching a cell, the problem was that I'm using more than one UIGesture without adding ".cancelsTouchesInView = false" for each one
so if you're using a UIGesture just add Your_Gesture.cancelsTouchesInView = false
and you should be able to access your cells
scrollViewWillBeginDecelerating: delegate method is called on finger up as it is moving.(from UIScrollView.h)
But, scrollViewDidEndDragging:willDecelerate: delegate method is also called when same state.
(called on finger up if the user dragged. decelerate is true if it will continue moving afterwards) -> from UIScrollView.h)
When I test, they are always called together.
I don't know what is the difference.
Actually, I should know when a scroll will be begun deceleration.
scrollViewWillBeginDecelerating: is always called on finger up.
No both has differrent states
scrollViewDidEndDragging - The scroll view sends this message when the user’s finger touches up after dragging content. The decelerating property of UIScrollView controls deceleration.
scrollViewWillBeginDecelerating - The scroll view calls this method as the user’s finger touches up as it is moving during a scrolling operation; the scroll view will continue to move a short distance afterwards. The decelerating property of UIScrollView controls deceleration.
Refer Here
If you drag the scroll view so slowly that after your finger is up, the scroll view won't move, then scrollViewDidEndDragging:willDecelerate: will be called, with decelerate == NO, while the scrollViewWillBeginDecelerating: not being called.
Both the delegate methods will be called when you drag fast enough.
I am interested in getting the touch location (e.g. something that is, or mimics touchesMoved) in a view controller's view while still keeping a UIScrollView subview enabled. Since all of the touchesDidSomething methods are consumed by the UIScrollView, my hope is that there's a roundabout way of achieving this.
Here are a few things I've tried:
Subclassing a UIScrollView, overriding it's touchesMoved method and passing that touch information to a custom delegate method in my scrollView's view controller. --> This actually works if I deselect "cancellable content touches" and "delays content touches" on my scroll view but it prevents my scroll view from scrolling.
Using the same tactic as above but with a subclassed UIView as a sibling to my UIScrollView. Hence, the hierarchy is as follows:
view
UIScrollView
Subview
Subview
CustomUIView <-- custom UIView that calls delegate
Both of these methods work to the extent that I can grab the data, but at the expense of my scroll view not scrolling anymore. I know I can grab the location of a touch event in a UIScrollView similar to a touchesBegan while the scrollView continues to work but I haven't found a way to get continuous touch events while scrolling. Is this possible?
Here's an illustration of what I'm after:
For some metadata as to why I'm looking for this and why grabbing something like scrollViewDidScroll's contentOffset.x won't work, I'm specifically interested in when the scrollView has scrolled to the end (or beginning) and a user is attempting to keep swiping forward (or backward) even though the scrollView can't scroll in the swiped direction anymore. When this happens I want to detect the forward (or backward) swipe motion to initiate a slick transition to another view.
I solved this by adding a panning gesture recognizer to my view and using it's translationInView method (UISwipeGestureRecognizer doesn't have this method). Example below
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(myMethod(_:)))
panGesture.delegate = self
self.addGestureRecognizer(panGesture)
func myMethod(sender: UIPanGestureRecognizer) {
// sender.translationInView(self).x
}
Also making sure to return true for shouldRecognizeSimultaneouslyWithGestureRecognizer in the UIGestureRegognizerDelegate to account for the scrollView.
I created a "slide view" (a UIView subclass) which animates on screen by dragging it up. The animation and everything else related to the animation works perfectly fine. This question targets only the very first touch on the screen when the slide view itself will be initialized:
The slide view itself uses the UIPanGestureRecognizerto recognize touches. The thing is, my slide view will be initialized only at the time when the user touches down a UIButton. Parts of the slide view are initially locates on that button, so that when the user touches that button, the touch is also located inside the slide view's frame.
I only want to create the view at the time the touch occurs, because the view is pretty heavy. I don't want to waste resources cause often the button is not even used.
How can I make the slide view recognize that first touch that also initializes (and adds it as a subview to super) the slide view itself?
You can check this out for more details:
Gestures
Well and you can add both gesture pan as well as tap gesture. It will definitely work as tap is not the first action of the pan gesture. So no need to wait for tap gesture to fail.
In short you can add both gestures and handle them simply.
I'm building an iOS layout which consists of a UITableView and a UIScrollView. The UIScrollView is inside a table cell of the UITableView and can be scrolled both horizontally and vertically. The diagram below shows this situation. If the user begins scrolling down/up on the UIScrollView the scrolling event should trigger setContentOffset of the table view, and not setContentOffset for the scroll view while the top of the scroll view will be on the dotted line (it's constant height). Then a scrolling touch event should trigger setContentOffset for the scroll view, not for the table view.
In another case: When the user starts scrolling on the table view, it should trigger setContentOffset for the table view, until the scroll view reaches the dotted line. Then the scroll view should handle setContentOffset.
My problem is how to transfer touch events between the table view and the scroll view during one sliding action.
This sounds like one of those cases where you want something quite specific and custom. So trying to do something clever with the gesture recognizers won't be enough.
The main problem is that the ways you can control gesture recognizers such as with gestureRecognizer:shouldReceiveTouch: and gestureRecognizerShouldBegin: only affect the start of the gesture (or for new touches, not ongoing ones), but you want a single ongoing gesture to transition between controlling each view. So for this reason I think you will need to place a large transparent view over your entire screen with a pan gesture recognizer on it and in your handlePan method decide which view you want to adjust and then call setContentOffset directly on that view. You can use the translation of the pan recognizer and the existing content offset to calculate the new one. I know this isn't very elegant, but I can't think of another way to achieve the effect you want.
I'm not sure if this is going to work, but you could try doing something like this:
Option
self.scrollView.panGestureRecognizer = self.tableView.panGestureRecognizer;
Option
[self.scrollView addGestureRecognizer:self.tableView.panGestureRecognizer];
Option
[self.tableView.panGestureRecognizer requireGestureRecognizerToFail:self.scrollView.panGestureRecognizer];