I'm using UIScrollView's canceling touch ability with canCancelContentTouches.
However, I 'd like the uiscrollview to attempt to cancel touch when it detected horizontal dragging(not vertical).
(Hope solution would be available under < iOS 3.13)
Thank you
Implement the UIScrollViewDelegate and then use something like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[scrollView setContentOffset: CGPointMake(0, scrollView.contentOffset.y)];
}
Another way would be having a UIScrollView which is smaller or equal to the size of its parent view and with a disabled "Always bounce horizontal".
The safest and most successful method I've found to constrain the movement of a scroll view is to subclass UIScrollView and override setContentOffset:animated: and setContentOffset: methods (code below).
The advantage of overriding these methods is that it directly alters the requested contentOffset before any of the UIKit code starts to act on it, avoiding any of the side effects that can occur when modifying the contentOffset in scrollViewDidScroll: or other UIScrollViewDelegate methods.
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated {
// restrict movement to horizontal only
CGPoint newOffset = CGPointMake(contentOffset.x, 0);
[super setContentOffset:newOffset animated:animated];
}
- (void)setContentOffset:(CGPoint)contentOffset {
// restrict movement to horizontal only
CGPoint newOffset = CGPointMake(contentOffset.x, 0);
[super setContentOffset:newOffset];
}
i guess i would use the method scrollViewWillBeginDragging
found in the UIScrollViewDelegate
and inside i could control if user is going horizontally or not...
scrollViewWillBeginDragging:
Tells the delegate when the scroll view is about to start scrolling the content.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
Parameters
scrollView
The scroll-view object that is about to scroll the content view.
Discussion
The delegate might not receive this message until dragging has occurred over a small distance.
Availability
Available in iOS 2.0 and later.
Related
Anyone has an idea how to control two scroll views while one in on the top of the other. One scrolls, the other one scrolls too. Same with zoom, gesture recognisers, etc ... Kinda like passing replica of the touches received by the first view onto the the one underneath. Subclass of the top scroll view has got a weak reference to the "dependant" scroll view underneath. Very important is to get the delegate methods working for both scrollviews as there is a lot of logic in these ...
use the scrollView Delegate methood
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if([scrollView isEqual:scrollViewA]) {
CGPoint offset = scrollViewB.contentOffset;
offset.y = scrollViewA.contentOffset.y;
[scrollViewB setContentOffset:offset];
} else {
CGPoint offset = scrollViewA.contentOffset;
offset.y = scrollViewB.contentOffset.y;
[scrollViewA setContentOffset:offset];
}
}
or simply in the same method for both horizontal an vertical scrolling
if([scrollView isEqual:scrollViewA]) {
scrollViewB.contentOffset = scrollViewA.contentOffset;
}
and viceVersa
I am using a UIWebView to show a PDF. I need to track some UIScrollView events, so I set the built-in UIScrollView like this:
for(UIScrollView *s in webView.subviews) {
s.delegate = self;
}
But the problem is that when I do this, I lose the ability to pinch to zoom (like I could before because the UIWebView's Scale Pages to Fit property was set). How come this happens? What can I do to fix it? I went ahead and implemented the UIScrollView shouldZoom method, but when I pass back the scrollView as the view to zoom on, it zooms from the corner, not where my fingers are.
Does anybody know a solution to this problem? How can I have the UIScrollView delegate set, and still retain natural zooming ability?
Thanks!
You are messing with some UIWebView internals. This feels like a bad hack, but I think you could forward all the UIScrollViewDelegate methods back to UIWebView in addition to doing whatever you need to do.
Also, UIWebView has many subviews. You should check and override the delegate just for the UIScrollView.
for (UIView *subview in webViews.subviews) {
if ([subview isKindOfClass:[UIScrollView class]]) {
subview.delegate = self;
}
}
Either handle the following events in your delegate:
– viewForZoomingInScrollView:
– scrollViewWillBeginZooming:withView:
– scrollViewDidEndZooming:withView:atScale:
– scrollViewDidZoom:
or save the original delegate
origDelegate = s.delegate;
s.delegate = self;
and forward the calls, e.g:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return [origDelegate viewForZoomingInScrollView:scrollView];
}
Setup: I have a UITableView, each UITableViewCell has a UIScrollView. What I am trying to do is to get all of the UIScrollViews to scroll together, such that when you scroll one of them all of the UIScrollViews appear to scroll simultaneously.
What I've done is subclass UITableView so that it has an array of all of the UIScrollViews within its table cells. I then forwarded TouchesBegan, TouchesMoved, TouchesCancelled, and TouchesEnded from the UITableView to all of the UIScrollViews in the array.
This doesn't appear to work. The UIScrollViews do not scroll! The only way I've managed to get this to work is to call the setContentOffset: method on the scrollviews. However, this is a pretty bad solution since it doesn't give you the swiping and deceleration features of the UIScrollView.
Any ideas on why my touches methods aren't getting to the UIScrollViews? Or a better way to implement this?
Ok, got it working. Thanks for the tips Ricki!
2 things to add to Ricki's solution, if you want to avoid an infinite loop, you have to check to see whether the scrollView's tracking or dragged properties are set. This will insure that only the ScrollView that is actually being dragged is calling the delegate.
- (void)scrollViewDidScroll:(UIScrollView *) theScrollView {
if (theScrollView.dragging || theScrollView.tracking)
[self.delegate scrolling:[theScrollView contentOffSet]];
}
Also, in the scrolling method of the delegate, I set animated to NO, this got rid of the delay between the initial swipe and the other scrollviews getting updated.
I did something "similar" where I had 4 scrollViews incased inside a parent view.
I placed a scrollView inside a UIView, this UIView was passed a delegate from its parentView, that was the view who kept track of all the scrollViews. The UIView containing a scrollVIew implemented the UIScrollViewDelegate and this method;
- (void)scrollViewDidScroll:(UIScrollView *) theScrollView {
[self.delegate scrolling:[self.scrollView contentOffSet]];
}
Now the parent view did this on all the scrollViews:
- (void) scrolling:(CGFloat) offset {
for(UIScrollView *s in self) {
[s setContentOffset:offset animated:YES];
}
}
It is of course a bit of a strain on the CPU, but scrolling several views will be that under any circumstances :/
Hope this was something in the direction of what you needed, and that it made any sense.
Added:
I took me 8 different paths and a lot of mass chaos before I made it work. I dropped the touchedBegan approach early, there is just no way to write something that comes close to Apples swipe, flick, scroll algorithms.
I don't know if the tableview and scrollview will "steal" each others touch events, but as I can read from your description you made that part work.
A follow up idea to ease the CPU usage. add each scrollview to a cell, set its tag=14, now when scrolling asked for all visible cells only, ask for viewWithTag=14, set the contentOffset on this. Save the content offset globally so you can assign it to cells being scrolled onto the screen in cellForRowAtIndexPath.
So set the offSet to a global property, in cellForRowAtIndexPath find the view with tag = 14, set its offset. This way you don't even need a reference to the scrollViews only the delegate.
If you have differently sized UIScrollViews and are using paging, this works great:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)_scrollView {
#pragma unused(_scrollView)
categoryPageControlIsChangingPage = NO;
for (UIImageView *iv in [categoryScrollView subviews]) {
iv.alpha = (iv.tag != categoryPageControl.currentPage+1)?0.5f:1.0f;
ILogPlus(#"%i %i", iv.tag, categoryPageControl.currentPage+1);
}
[self scrolling:_scrollView];
}
- (void)scrolling:(UIScrollView *)sv {
CGFloat offsetX = sv.contentOffset.x;
CGFloat ratio = offsetX/sv.contentSize.width;
if ([sv isEqual:categoryScrollView]) {
[categoryScrollViewLarge setContentOffset:CGPointMake(ratio*categoryScrollViewLarge.contentSize.width, 0) animated:YES];
}else {
[categoryScrollView setContentOffset:CGPointMake(ratio*categoryScrollView.contentSize.width, 0) animated:YES];
}
}
I am looking for a way to create a scrollViewIsZooming method that is called while zooming is occuring. Does anyone know of a way to do this?
I am wanting to use it to keep the content centered in the scrollView while zooming. If I use the scrollViewDidEndZooming method, the content snaps back to the center after zooming is finished.
Thanks!
There's a zooming propriety on UIScrollView :
#property(nonatomic, readonly, getter=isZooming) BOOL zooming
If you check it in each
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
call it should work.
What about viewForZoomingInScrollView:
If you put a pinch gesture recogniser in the view then connect it to an action you can control zooming that way. I have an action "doPinch" and an outlet "pinchRecognizer" that I use like this:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.zoomableImage;
}
Then
- (IBAction)doPinch:(id)sender
{
NSLog(#"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}
Might be easier. Note that scrollView is my outlet connected to the scroll view.
Is there a way to deactivate the decelerating of a UIScrollView?
I want to allow the user to scroll the canvas, but I don't want that the canvas continues scrolling after the user lifted the finger.
This can be done by utilizing the UIScrollView delegate method scrollViewWillBeginDecelerating to automatically set the content offset to the current screen position.
To implement:
Assign a delegate to your UIScrollView object if you have not already done so.
In your delegate's .m implementation file, add the following lines of code:
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
[scrollView setContentOffset:scrollView.contentOffset animated:YES];
}
Voila! No more auto-scroll.
For iOS 5.0 or later, there is a better method than calling setContentOffset:animated:.
Implement delegate method scrollViewWillEndDragging:withVelocity:targetContentOffset: in your .m file:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset {
targetContentOffset.pointee = scrollView.contentOffset;
}
Assigning the current offset to targetContentOffset stops the UIScrollView from auto-scrolling.
You can just turn up the deceleration rate very high. With an infinite rate, it would stop immediately. Try setting the rate to these constants:
scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
and
scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
If fast still isn't fast enough for you, UIScrollViewDecelerationRateFast is just typedef'ed as a float, so you can just multiply it by a factor of 10 or so to speed it up even more.
Just set the decelerationRate property to 0
It will disable the auto scrolling property. But keep in mind the user interaction will become bad if scrollview contentsize is big.
Previous Swift version:↓
scrollView.decelerationRate = UIScrollView.DecelerationRate.fast
Current Swift 4.2 version code:↓
scrollView.decelerationRate = UIScrollViewDecelerationRateFast