Force UICollectionView to stop scrolling - ios

A user performs a quick swipe gesture to make a UICollectionView start scrolling (it will gradually come to an halt).
How can I programmatically force the scrolling to come to an immediate stop?
To clarify, I want to allow the deceleration but I need to be able to stop it in code.

Have you tried the following?
[self.collectionView setContentOffset:self.collectionView.contentOffset animated:NO];
the contentOffset property is constantly updated as the collectionView scrolls (even via animation) so at the time of calling the above, it should hopefully force the collectionView to stop its existing animation.

Try this one. Worked for me. :)
self.collectionView.scrollEnabled = NO;

For Swift 3:
collectionView.isScrollEnabled = false

if you have the pagingEnabled and scrollEnabled properties set to true than this should work:
self.collectionView.scrollEnabled = false
self.collectionView.pagingEnabled = false

Adopt the following scrollViewDelegate method to pick up when the user lets go of dragging the collectionView.
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset;
You can then just create your own animation block to set whichever speed/final destination you think looks best using the contentOffset property.

Related

How to cancel UIScrollView bounce animation after dragging? [duplicate]

This question already has answers here:
Cancel UIScrollView bounce after dragging
(2 answers)
Closed 7 years ago.
How to cancel UIScrollView bounce animation after dragging? ( NOT DISABLE BOUNCE )
Thanks!
some kind effect like dragRefresh? If it is, you should set your scrollView contentInset at scrollView end drag delegate method.
set the content offset in the scrollView delegate method scrollViewWillEndDragging:withVelocity:targetContentOffset:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset;
{
scrollView.contentOffset = //.. content offset of where you want it to stop, use the current offset if you want it to stay where it is.
}
Your question is vague and doesn't give me much to go on to provide a better answer, hope this is of help.
[scrollView setContentOffset:scrollView.contentOffset animated:NO];
NOTE: You must use the animated version of the function - not the version without the animated flag.

Cross Directional UIScrollViews - Can I Modify the Scrolling Behaviour?

Here's how the scroll views work: One scroll view is paging enabled in the horizontal direction. Each 'page' of this scroll view contains a vertically scrolling UITableView. Without modification, this works OK, but not perfectly.
The behaviour that's not right: When the user scrolls up and down on the table view, but then wants to flick over to the next page quickly, the horizontal flick/swipe will not work initially - it will not work until the table view is stationary (even if the swipe is very clearly horizontal).
How it should work: If the swipe is clearly horizontal, I'd like the page to change even if the table view is still scrolling/bouncing, as this is what the user will expect too.
How can I change this behaviour - what's the easiest or best way?
NOTE For various reasons, a UIPageViewController as stated in some answers will not work. How can I do this with cross directional UIScrollViews (/one is a table view, but you get the idea)? I've been banging my head against a wall for hours - if you think you can do this then I'll more than happily award a bounty.
According to my understanding of the question, it is only while the tableView is scrolling we want to change the default behaviour. All the other behaviour will be the same.
SubClass UITableView. UITableViews are subClass of UIScrollViews. On the UITableView subClass implement one UIScrollView's UIGestureRecognizer's delegate method
- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer *)otherGestureRecognizer
{
//Edit 1
//return self.isDecelerating;
//return self.isDecelerating | self.bounces; //If we want to simultaneous gesture on bounce and scrolling
//Edit 2
return self.isDecelerating || self.contentOffset.y < 0 || self.contentOffset.y > MAX(0, self.contentSize.height - self.bounds.size.height); // #Jordan edited - we don't need to always enable simultaneous gesture for bounce enabled tableViews
}
As we only want to change the default gesture behaviour while the tableView is decelerating.
Now change all 'UITableView's class to your newly created tableViewSubClass and run the project, swipe should work while tableView is scrolling. :]
But the swipe looks a little too sensitive while tableView is scrolling. Let's make the swipe a little restrictive.
SubClass UIScrollView. On the UIScrollView subclass implement another UIGestureRecognizer's delegate method gestureRecognizerShouldBegin:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self];
if (abs(velocity.y) * 2 < abs(velocity.x)) {
return YES;
}
}
return NO;
}
We want to make the "swipe is clearly horizontal". Above code only permits gesture begin if the gesture velocity on x axis is double than on y axis. [Feel free to increase the hard coded value "2" if your like. The higher the value the swipe needs to be more horizontal.]
Now change the `UiScrollView' class (which has multiple TableViews) to your ScrollViewSubClass. Run the project. :]
I've made a project on gitHub https://github.com/rishi420/SwipeWhileScroll
Although apple doesn't like this method too much:
Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behavior can result
because touch events for the two objects can be mixed up and wrongly
handled.
I've found a great way to accomplish this.
This is a complete solution for the problem. In order to scroll the UIScrollView while your UITableView is scrolling you'll need to disable the interaction you have it.
- (void)viewDidLoad
{
[super viewDidLoad];
_myScrollView.contentSize = CGSizeMake(2000, 0);
data = [[NSMutableArray alloc]init];
for(int i=0;i<30;i++)
{
[data addObject:[NSString stringWithFormat:#"%d",i]];
}
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self.view addGestureRecognizer:tap];
}
- (void)handleTap:(UITapGestureRecognizer *)recognizer
{
[_myTableView setContentOffset:_myTableView.contentOffset animated:NO];
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = YES;
}
To sum up the code above, if the UITableView is scrolling, set userInteractionEnabled to NO so the UIScrollView will detect the swipe. If the UITableView is scrolling and the user taps on the screen, userInteractionEnabled will be set to YES.
Instead of using UIScrollView as a container for these multiple table views, try using a UIPageViewController.
You can even integrate this into your existing view controller setup as a child view controller (directly replacing the UIScrollView).
In addition, you'll likely want to implement the required methods from UIPageViewControllerDataSource and possibly one or more of the methods from UIPageViewControllerDelegate.
Did you try the methods : directionalLockEnabled of both your table and scroll and set them up to horizontal for one and vertical for the other ?
Edit :
1)
What you want to do is very complicate since the touch wait some time (like 0.1s) to know what your movement will be. And if your table is moving, it will take your touch immediately whatever it is (because it's suppose to be reactive movement on it).
I don't see any other solution for you but to override touch movement from scratch to detect immediately the kind of mouvement you want (like if the movement will be horizontal) but it will be more than hard to do it good.
2)
Another solution I can advise you is to make your table have left and right margin, where you can touch the parent scroll (pages thing so) and then even if your table is scrolling, if you touch here, only your paging scroll will be touched. It's simpler, but could not fit with your design maybe...
Use UIPageViewController and in the -viewDidLoad method (or any other method what best suits your needs or design) get UIPageViewController's UIScrollView subview and assign a delegate to it. Keep in mind that, its delegate property won't be nil. So optionally, you can assign it to another reference, and then assign your object, which conforms to UIScrollViewDelegate, to it. For example:
id<UIScrollViewDelegate> originalPageScrollViewDelegate = ((UIScrollView *)[pageViewController.view.subviews objectAtIndex:0]).delegate;
[((UIScrollView *)[pageViewController.view.subviews objectAtIndex:0]) setDelegate:self];
So that you can implement UIScrollViewDelegate methods with ease. And your UIPageViewController will call your delegate's -scrollViewDidScroll: method.
By the way, you may be obliged to keep original delegate, and respond to delegate methods with that object. You can see an example implementation in ViewPagerController class on my UI control project here
I faced the same thing recently. My UIScrollview was on paging mode and every page contained a UITableView and like you described it worked but not as you'd expected it to work. This is how solved it.
First I disabled the scrolling of the UIScrollview
Then I added a UISwipeGestureRecognizer to the actual UITableView for left and right swipes.
The action for those swipes were:
[scroll setContentOffset:CGPointMake(currentPointX + 320, PointY) animated:YES];
//Or
[scroll setContentOffset:CGPointMake(currentPointX - 320 , PointY) animated:YES];
This works flawlessly, the only down side is that if the user drags his finger on the UITableVIew that will be considered as a swipe. He won't be able to see half of screen A and half of screen B on the same screen.
You could subclass your scroll view and your table views, and add this gesture recognizer delegate method to each of them...
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
I can't be sure this is exactly what you are after, but it may come close.

Can I disable / edit the automatic jump-to-top scroll when tapping status bar?

I'm using an app with a tableView that auto-scrolls downward, so tapping the status bar, which would normally jump to the top of the table, causes problems (it begins scrolling but if the auto-scroll ticks, it stops and leaves it somewhere in the middle).
I'd either like to disable it, or at least have shove some code in when it's tapped to temporarily pause the timer and then resume it when it reaches the top.
Are there any means of achieving either of these things?
UIScrollView (and UITableView as a subclass of it) has scrollsToTop property which defaults to YES, making it scroll to top when status bar is tapped. All you have to do is set it to NO.
However, please keep in mind that iOS users might expect this behavior, so turning it off may not be the best idea from user experience standpoint. You can also leave it as YES and return NO from -scrollViewShouldScrollToTop: delegate method if you only need it disabled at specific times.
Actually, handling it via delegate might be a perfect fit for your case:
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
// disable timer
return YES;
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{
// re-enable timer
}
You can try:
[myView setScrollsToTop:NO];
For swift 5
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
return false
}

scrollViewDidEndScrollingAnimation not getting called

I want to perform some action when scrollview finish scrolling, so I wrote that acton in scrollViewDidEndScrollingAnimation delegate method. It is working fine when rect is not visible and scrollview scrolls to new rect. But when rect is already visible scrollViewDidEndScrollingAnimation method will not be called and so the method written inside scrollViewDidEndScrollingAnimation will not get called. But I want to call that action, can anyone knows how to call that method when scrollview finish scrolling?
Thanks in advance!
I think this one catches a few people out. What actually happens is that scrollViewDidEndScrollingAnimation is only called if you explicitly invoke either the setContentOffset:animated: or the scrollRectToVisible:animated: methods.
As the UIScrollViewDelegate Protocol Reference states:
Discussion
The scroll view calls this method at the end of its
implementations of the setContentOffset:animated: and
scrollRectToVisible:animated: methods, but only if animations are
requested.
So what to do? Well, let's not forget that typically the offsetting of content data in a scroll view is not animated. Rather, it is the result of continuously updating the contentOffset value. So, you could possibly trigger your method based upon a specific contentOffset using the scrollViewDidScroll: delegate method.
Alternatively, if it is something to be done after every scroll gesture - specifically, after the private UIScrollViewPanGestureRecognizer - then you could do it in scrollViewDidEndDecelerating::
Discussion The scroll view calls this method when the scrolling
movement comes to a halt. The decelerating property of UIScrollView
controls deceleration.
Implement both scrollViewDidEndDecelerating: and scrollViewDidEndDragging:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// User lifted finger while scrolling
[self doPostScrollAction];
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if(!decelerate)
{
// User lifted finger after stopping scrolling
[self doPostScrollAction];
}
}

Stop UIWebView from "bouncing" vertically?

Does anyone know how to stop a UIWebView from bouncing vertically? I mean when a user touches their iphone screen, drags their finger downwards, and the webview shows a blank spot above the web page I had loaded?
I've looked at the following possible solutions, but none of them worked for me:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/996-turn-off-scrolling-bounces-uiwebview.html
http://forums.macrumors.com/showthread.php?t=619534
How do I stop a UIScrollView from bouncing horizontally?
for (id subview in webView.subviews)
if ([[subview class] isSubclassOfClass: [UIScrollView class]])
((UIScrollView *)subview).bounces = NO;
...seems to work fine.
It'll be accepted to App Store as well.
Update: in iOS 5.x+ there's an easier way - UIWebView has scrollView property, so your code can look like this:
webView.scrollView.bounces = NO;
Same goes for WKWebView.
I was looking at a project that makes it easy to create web apps as full fledged installable applications on the iPhone called QuickConnect, and found a solution that works, if you don't want your screen to be scrollable at all, which in my case I didn't.
In the above mentioned project/blog post, they mention a javascript function you can add to turn off the bouncing, which essentially boils down to this:
document.ontouchmove = function(event){
event.preventDefault();
}
If you want to see more about how they implement it, simply download QuickConnect and check it out.... But basically all it does is call that javascript on page load... I tried just putting it in the head of my document, and it seems to work fine.
Well all I did to accomplish this is :
UIView *firstView = [webView.subviews firstObject];
if ([firstView isKindOfClass:[UIScrollView class]]) {
UIScrollView *scroll = (UIScrollView*)firstView;
[scroll setScrollEnabled:NO]; //to stop scrolling completely
[scroll setBounces:NO]; //to stop bouncing
}
Works fine for me...
Also, the ticked answer for this question is one that Apple will reject if you use it in
your iphone app.
In the iOS 5 SDK you can access the scroll view associated with a web view directly rather than iterating through its subviews.
So to disable 'bouncing' in the scroll view you can use:
myWebView.scrollView.bounces = NO;
See the UIWebView Class Reference.
(However if you need to support versions of the SDK before 5.0, you should follow Mirek Rusin's advice.)
Swift 3
webView.scrollView.bounces = false
Warning. I used setAllowsRubberBanding: in my app, and Apple rejected it, stating that non-public API functions are not allowed (cite: 3.3.1)
In Swift to disable bounces
webViewObj.scrollView.bounces = false
Brad's method worked for me. If you use it you might want to make it a little safer.
id scrollView = [yourWebView.subviews objectAtIndex:0];
if( [scrollView respondsToSelector:#selector(setAllowsRubberBanding:)] )
{
[scrollView performSelector:#selector(setAllowsRubberBanding:) withObject:NO];
}
If apple changes something then the bounce will come back - but at least your app won't crash.
On iOS5 only if you plan to let the users zoom the webview contents (e.i.: double tap) the bounce setting isn't enough. You need to set also alwaysBounceHorizontal and alwaysBounceVertical properties to NO, else when they zoom-out (another double tap...) to default it will bounce again.
I traversed the collection of UIWebView's subviews and set their backgrounds to [UIColor blackColor], the same color as the webpage background. The view will still bounce but it will not show that ugly dark grey background.
It looks to me like the UIWebView has a UIScrollView. You can use documented APIs for this, but bouncing is set for both directions, not individually. This is in the API docs.
UIScrollView has a bounce property, so something like this works (don't know if there's more than one scrollview):
NSArray *subviews = myWebView.subviews;
NSObject *obj = nil;
int i = 0;
for (; i < subviews.count ; i++)
{
obj = [subviews objectAtIndex:i];
if([[obj class] isSubclassOfClass:[UIScrollView class]] == YES)
{
((UIScrollView*)obj).bounces = NO;
}
}
I was annoyed to find out that UIWebView is not a scroll view, so I made a custom subclass to get at the web view's scroll view. This suclass contains a scroll view so you can customize the behavior of your web view. The punchlines of this class are:
#class CustomWebView : UIWebview
...
- (id) initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
// WebViews are subclass of NSObject and not UIScrollView and therefore don't allow customization.
// However, a UIWebView is a UIScrollViewDelegate, so it must CONTAIN a ScrollView somewhere.
// To use a web view like a scroll view, let's traverse the view hierarchy to find the scroll view inside the web view.
for (UIView* v in self.subviews){
if ([v isKindOfClass:[UIScrollView class]]){
_scrollView = (UIScrollView*)v;
break;
}
}
return self;
}
Then, when you create a custom web view, you can disable bouncing with:
customWebView.scrollView.bounces = NO; //(or customWebView.scrollView.alwaysBounceVertically = NO)
This is a great general purpose way to make a web view with customizable scrolling behavior. There are just a few things to watch out for:
as with any view, you'll also need to override -(id)initWithCoder: if you use it in Interface Builder
when you initially create a web view, its content size is always the same as the size of the view's frame. After you scroll the web, the content size represents the size of the actual web contents inside the view. To get around this, I did something hacky - calling -setContentOffset:CGPointMake(0,1)animated:YES to force an unnoticeable change that will set the proper content size of the web view.
Came across this searching for an answer and I eventually just lucked on an answer of my own by messing about. I did
[[webview scrollView] setBounces:NO];
and it worked.
This worked for me, and beautifully too (I am using phonegap with webView)
[[webView.webView scrollView] setScrollEnabled:NO];
or
[[webView scrollView] setScrollEnabled:NO];
I tried a slightly different approach to prevent UIWebView objects from scrolling and bouncing: adding a gesture recognizer to override other gestures.
It seems, UIWebView or its scroller subview uses its own pan gesture recognizer to detect user scrolling. But according to Apple's documentation there is a legitimate way of overriding one gesture recognizer with another. UIGestureRecognizerDelegate protocol has a method gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: - which allows to control the behavior of any colliding gesture recognizers.
So, what I did was
in the view controller's viewDidLoad method:
// Install a pan gesture recognizer // We ignore all the touches except the first and try to prevent other pan gestures
// by registering this object as the recognizer's delegate
UIPanGestureRecognizer *recognizer;
recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
recognizer.delegate = self;
recognizer.maximumNumberOfTouches = 1;
[self.view addGestureRecognizer:recognizer];
self.panGestureFixer = recognizer;
[recognizer release];
then, the gesture override method:
// Control gestures precedence
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// Prevent all panning gestures (which do nothing but scroll webViews, something we want to disable in
// the most painless way)
if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
// Just disable every other pan gesture recognizer right away
otherGestureRecognizer.enabled = FALSE;
}
return NO;
}
Of course, this delegate method can me more complex in a real application - we may disable other recognizers selectively, analyzing otherGestureRecognizer.view and making decision based on what view it is.
And, finally, for the sake of completeness, the method we registered as a pan handler:
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
// do nothing as of yet
}
it can be empty if all we want is to cancel web views' scrolling and bouncing, or it can contain our own code to implement the kind of pan motions and animations we really want...
So far I'm just experimenting with all this stuff, and it seems to be working more or less as I want it. I haven't tried to submit any apps to iStore yet, though. But I believe I haven't used anything undocumented so far... If anyone finds it otherwise, please inform me.
Here's two newer potential solutions. Apparently, you can use jqtouch or pastrykit to disable scrolling. However, I haven't got these to work. You might be more competent.
turning off vertical scrolling
digging into pastrykit
fixed positioning on mobile safari
This link helped me lot.....Its easy.. There is a demo..
(Xcode 5 iOS 7 SDK example) Here is a Universal App example using the scrollview setBounces function. It is an open source project / example located here: Link to SimpleWebView (Project Zip and Source Code Example)
One of the subviews of UIWebView should be a UIScrollView. Set its scrollEnabled property to NO and the web view will have scrolling disabled entirely.
Note: this is technically using a private API and thus your app could be rejected or crash in future OS releases. Use #try and respondsToSelector
Look into the bounces property of UIScrollView. Quoth the Apple docs:
If the value of the property is YES (the default), the scroll view bounces when it encounters a boundary of the content. Bouncing visually indicates that scrolling has reached an edge of the content. If the value is NO, scrolling stops immediately at the content boundary without bouncing.
Make sure you're using the right UIScrollView. I'm not sure what the hierarchy looks like for a UIWebView, but the scroll view could be a parent, not a child, of the UIWebView.
To disable UIWebView scrolling you could use the following line of code:
[ObjWebview setUserInteractionEnabled:FALSE];
In this example, ObjWebview is of type UIWebView.
webView.scrollView.scrollEnabled=NO;
webView.scrollView.bounces=NO;

Resources