UIScrollView - tell the difference between setContentOffset and manual scrolling - ios

I have a scrollview, which automatically advances every 3 seconds to the next "page" (page control also used). However, once the user touches the scrollview, I want to stop the auto-advancing.
I have subclassed the scrollview. I can detect touchesBegan in the subclass, but touchesMoved is not called, so I can't tell if the user has manually swiped the scrollview. I can't use scrollviewDidScroll, because that gets called when I set the contentOffset when the auto-advance timer fires.
So what are my other options for detecting touches? Why isn't touchesMoved called on the scrollview subclass?

Thank you for the suggestions. They helped me stumble upon this easy solution:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self.scrollTimer invalidate];
}

You may want to look into the following delegate method:
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
Per Apple:
The scroll view calls this method at the end of its implementations of the UIScrollView and setContentOffset:animated: and scrollRectToVisible:animated: methods, but only if animations are requested.
So that delegate method is only called when programatic scrolling occurs. You could set up your autoscroll call in that method, only calling it if some BOOL is false. Setting that BOOL to true in your touch event.
Or something completely different =]
It is a useful delegate method though.
~ Good luck

The scrollview probably nominates a subview to receive touch input — UIKit objects are very fussy about this sort of thing and generally can't even handle forwarded events.
What you probably want to do is to key-value observe either tracking or dragging (and it sounds like you want the latter). If the relevant property changes to true then you know the user has initiated scrolling.

Related

UIScrollView pass event to child chain on WillEndDragging

Edit: I am editing my initial question (see below for history) as I am getting new information.
I figured out that when the swipe motion starts from inside the button bounds, we never receive TouchesEnded or TouchesCancelled, only TouchesMoved. However, if I can react on WillEnddragging, it would be great. Is it possible to cancel a gesture on WillEndDragging and also pass this cancel down the children chain?
History:
I am using Xamarin Forms and I have the following issue
I have custom controls part of native scrolling views, like ScrollView or CollectionView, that remain in "clicked" state after the finger enters them but then initiates a scroll gesture.
I had a similar issue on UWP in the past and managed to solve it with the UIElement.PointerCaptureLost event.
Sorry if I am wasting your time on trivial stuff, but I am really stuck and I greatly appreciate your help.
I have tried different approaches suggested, including setting DelaysContentTouches to NO, and playing around with CanCancelContentTouches and overriding TouchesShouldCancelInContentView to always return NO, in a ScrollView custom renderer.
I have had a read of
Allow UIScrollView and its subviews to both respond to a touch
and
UIScrollView sending touches to subviews
Maybe the accepted answer here helps, but I am not sure how to get the tag of my custom view.
What I am expecting is my custom controls to receive the cancelled touch event (or something similar) as happens in both Android and Windows
This was easier than it looked. Solved by adding a UIGestureRecognizerDelegate to my UIGestureRecognizer class and in the delegate I overwrote ShouldRecognizeSimultaneously to return true.

iOS UITableView prevent from scrolling

I want to get information about how UITableView would be scrolled without actually schange scrollView.contentOffset. I cant use scrollEnabled property becouse its stop fireing scrollViewDidScroll event on table and I need it. Is there any way to do it?
The question is a little bit hard to understand but in any case scrolling on a UITableView will modify contentOffset. That's how the superclass, UIScrollView, works.
As for scrollViewDidScroll, it is only called automatically when the user scrolls the view, but you can override setContentOffset to trigger it manually when needed. Just to avoid a loop set a BOOL dragging = YES flag on scrollViewWillBeginDragging: and clear it on scrollViewDidEndDragging:willDecelerate:.

UIScrollView scrollViewBeginDecelerating not called

In my scrollview, I have successfully used scrollViewDidEndDecelerating in my App. However, I'm adding a video player and would like to have it loaded prior to the scroll taking place. I located the delegate method scrollViewBeginDecelerating in Apple's docs, but it doesn't get called. Here is the code:
#pragma mark - UIScrollView Delegate
- (void)scrollViewBeginDecelerating:(UIScrollView *)sender
{
// setup movie view
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// previously used to display header text, etc
}
You need to implement scrollViewWillBeginDragging: and scrollViewDidScroll:. These will give you information when the user starts scrolling. Deceleration delegate calls will only start, once the user has lifted his or her finger, and there is still inertia.
The methods declared by the UIScrollViewDelegate protocol allow the adopting delegate to respond to messages from the UIScrollView class and thus respond to, and in some affect, operations such as scrolling, zooming, deceleration of scrolled content, and scrolling animations.
The scroll view’s dragging property is set to YES, and its delegate is sent the scrollViewWillBeginDragging: message
The Complete Delegate Message Sequence
When the user touches the screen the tracking sequence begins. The tracking property is set to YES immediately, and remains YES as long as the user’s finger is in contact with the screen, regardless of whether they are moving their finger.
https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/UIScrollView_pg/ScrollingViewContent/ScrollingViewContent.html

Touch events on subclass of UIView as a subview of UIScrollView

I have implemented my own custom subclass of UIView and overridden the drawRect: method.
In my custom view I also want the handle touches, so I also overridden touchesBegan, touchesMoved and touchesEnded.
This works fine but if the number of views on the screen increases then I have to use a UIScrollView as the root view of my UIViewController.
Once my custom UIView becomes the subview of UIScrollView, then it does not receive the touch events. Even though I move my finger within my custom UIView, the scroll view gets scrolled (all my touch events go to the UIScrollView).
How do I solve this problem?
There are several approaches you could try:
Try setting the below properties on the UIScrollView:
scrollView.delaysContentTouches = NO;
scrollView.canCancelContentTouches = NO;
See similar SO questions/answers here, here.
Implement hitTest:withEvent:. See here, here.
Use a UIGestureRecognizer. See here, here.
I would personally recommend using a UIGestureRecognizer, but it depends on your specific situation (any of these options may work fine for you).
Have a look at this response from another question: https://stackoverflow.com/a/4629821/193254
You'll have to subclass the scrollview too, and implement that hitTest: method.

UIPanGestureRecognizer.maximumNumberOfTouches not respected in nested scroll views?

I have a root UIScrollView that only scrolls vertically, this scrollview represents rows in my jagged grid. I have configured this scroll view's pan gesture recognizer for two touches for both minimum and maximum number of touches requires.
Inside this scrollview I have one or more UIScrollView instances that only scrolls horizontally, these scrollviews each represent a single row in my jagged grid view. I have configured the pan gesture recognizers for all of these scroll views for one touch minimum, and two touches maximum.
So far it works, I get a nice jagged grid view where I can scroll vertically between rows, and horizontally to scroll each row independently. I have intentionally set to minimum number of touches as 2, as not to inter fear with scrolling if I add fro example a UITableView as a subview for any of cell within this jagged grid view (cell == a position defined by a row and column in that row).
Using a UITableView as a cell works, the table view works as expected that is. But scrolling with two fingers also scrolls inside the table view, not at the root scroll view for vertically scrolling between rows.
I have tried configuring the table views pan gesture recognizer to allow a maximum of one touches, in hope that two finger touches would be ignored. This does not work, the maximumNumberOfTouches property of the table view's pan gesture recognizer seams to be ignored.
What could I have done wrong?
A screen shot displaying the layout to clarify what I have done:
Multiple scrolling tends to get tricky, and I don't for sure, but I think Apple does not encourage this. Even so, I still think it's possible. It may be that vertical scrolling on the table view gets mixed with the scroll view vertical scrolling or something else.
Try checking if the delegates for the gesture recognizers are correctly set.
Another way around this is:
- having a Scroll view with buttons, from which you can open popovers with custom controllers (insert there whatever you want).
- create a big UITableViewController and setting the cell's contents as scrollviews etc. I think you could get the same result.
My advice is not to get stuck on just one method, when there could be others more simpler and more intuitive.
TableViews on Scroll views are generally not a great idea. When a TableView receives the touches, even if doesn't need to do anything with it, it won't send them to it's superView.
You might wanna try either of these 2 things:
In your TableView you should send the touches to your superView manually and let them handle them appropriately. I've seen this method being used in one of my side-projects but I'm not able to post an example of it at this time.
The second thing might be easier to implement. Since TableView is a subclass of ScrollView you can call upon the delaysContentTouches of those TableViews. This property will delay the touch-down even on that TableView until it can determine if scrolling is the intent, as is written in the AppleDocs: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIScrollView_Class/Reference/UIScrollView.html#//apple_ref/occ/cl/UIScrollView
Let me know if either of the 2 ways works for you, I'm quite curious about this subject generally.
But don't you try some tricks rather than implementing all such changes :
1) By Default, disable the scrolling of the TableView when the view is created.
2) Once the view gets generated, Recognize the gestures whether its Scrolling using single or multiple touches, if user touch the Child Scrollview.Look out the tag ,based on gestures, you can enable the Scrolling of Tableview.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//Get the tag of ScrollView
// Check for the Parent as well as Child SCrollview.
// If its child, just enable the scrolling of tableView.
}
- (void)tapAction:(UIGestureRecognizer *)gestureRecognizer
{
// CGPoint *poit = [Tile locationInView:gestureRecognizer.view];
/// [[[gestureRecognizers.view] objectAtIndex:0] removeFromSuperview];
// imageContent = [[UIImageView alloc]initWithFrame:CGRec tMake(0, 0, 200, 250)];
// [imageContent setImage:[UIImage imageNamed:#"Default.png"]];
NSLog(#"You tapped # this :%d",gestureRecognizer.view.tag);
//Regonize the gestures
}
There may be some unnecessary code since, there is no code snippet with your question.I gave a try & showed a trick if that could work for you & solve the problem. ;)
Try this link and pay attention to how they solve nested views.
Remember the best Practices for Handling Multitouch Events:
When handling events, both touch events and motion events, there are a few recommended techniques and patterns you should follow.
Always implement the event-cancellation methods.
In your implementation, you should restore the state of the view to what it was before the current multitouch sequence, freeing any transient resources set up for handling the event. If you don’t implement the cancellation method your view could be left in an inconsistent state. In some cases, another view might receive the cancellation message.
If you handle events in a subclass of UIView, UIViewController, or (in rare cases) UIResponder,
You should implement all of the event-handling methods (even if it is a null implementation).
Do not call the superclass implementation of the methods.
If you handle events in a subclass of any other UIKit responder class,
You do not have to implement all of the event-handling methods.
But in the methods you do implement, be sure to call the superclass implementation. For example,
[super touchesBegan:theTouches withEvent:theEvent];
Do not forward events to other responder objects of the UIKit framework.
The responders that you forward events to should be instances of your own subclasses of UIView, and all of these objects must be aware that event-forwarding is taking place and that, in the case of touch events, they may receive touches that are not bound to them.
Custom views that redraw themselves in response to events should only set drawing state in the event-handling methods and perform all of the drawing in the drawRect: method.
Do not explicitly send events up the responder (via nextResponder); instead, invoke the superclass implementation and let the UIKit handle responder-chain traversal.

Resources