How do I avoid the jerking when I do an Infinite Horizontal Scroll? - objective-c

Platform: iOS 10+
I'm able to make a crude infinite scrolling loop via watching WWDC11.
However I can't figure out the algorithm to smooth out the jerking effect.
Any ideas would be appreciated.
Here's the collection view, to be scrolled horizontally:
Here's the UICollectionView where I'm attempting to create an infinite horizontal scroll effect (allowing for the initial scroll to trigger it):
#implementation PhotoCollectionView
- (void)recenterIfNecessary {
CGPoint currentOffset = self.contentOffset;
CGFloat contentWidth = self.contentSize.width;
CGFloat centerOffsetX = (contentWidth - self.bounds.size.width) / 2.0;
CGFloat distanceFromCenter = fabs(currentOffset.x - centerOffsetX);
if (contentWidth > 0 && (distanceFromCenter > (contentWidth / 2.5))) {
self.contentOffset = CGPointMake(centerOffsetX, currentOffset.y);
// Move content by the same amount so it appears to stay still.
// Note: Need to determine correct algorithm.
// Ran out-of-time on this one.
}
}
- (void)layoutSubviews {
[super layoutSubviews];
[self recenterIfNecessary];
}
#end
How do I provide a SMOOTH scroll vs the cell-replenish jerk?
...I want to also use this within Swift 4+ as well as Objective-C.

Related

iOS 8.0.2 : UIView frame animation not working when UINavigationController contained inside RootViewController

I've created a RootViewController / RootView that:
Handles the content layout for the app
Exposes and interface for performing application level behaviors, like presenting the "hamburger" menu or overlay views with CAKeyframe animations.
This is in accordance with good practice.
The Problem:
When the main content view presents a form, there's a utility to animate the frame of that view, when a field is selected that would otherwise be obscured by the keyboard. This has been working fine all the way up until iOS 8.0.2
On iOS 8.0.2 the frame for the form will no longer animate if you set a negative value for origin.y. Instead of going from the current origin.y to the required origin.y it jerks down by the amount it was supposed to move, then animates back to 0.
If I present the form outside of the RootVC it works correctly.
What I've tried:
Checked that RootView is not doing anything in layout subviews to prevent the animation. (In iOS8.0 it was. I removed this and problem was solved. Only to return in iOS8.0.2)
Checked the BeginFromCurrentState flags.
Instead of animating the form view, animate [UIScreen mainScreen].keyWindow. Works but causes some other side effects that I don't want.
Question:
What has changed with animation of UIViewControllers that are contained in another view in iOS8.0.2. It seems to be something very fundamental.
The code that animates frame to move input fields out the keyboard's way:
Looks something like this:
- (void)scrollToAccommodateField:(UIView *)view
{
UIView *rootView = [UIApplication sharedApplication].keyWindow.rootViewController.view;
CGPoint position = [view convertPoint:view.bounds.origin toView:rootView];
CGFloat y = position.y;
CGFloat scrollAmount = 0;
CGFloat margin = 25;
CGSize screenSize = [self screenSizeWithOrientation:[UIApplication sharedApplication].statusBarOrientation];
CGSize accessorySize = CGSizeMake(_view.width, 44);
CGFloat maxVisibleY = screenSize.height - [self keyboardSize].height - accessorySize.height - margin;
if (y > maxVisibleY)
{
scrollAmount = maxVisibleY - y;
CGFloat scrollDelta = scrollAmount - _currentScrollAmount;
_currentScrollAmount = scrollAmount;
[self scrollByAmount:scrollDelta];
}
else
{
if (_currentScrollAmount != 0)
{
_currentScrollAmount = 0;
[UIView transitionWithView:_view duration:0.30
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut animations:^
{
_view.frame = [_view bounds];
} completion:nil];
}
}
}
Update:
I've since installed TPKeyboardAvoiding pod and its working very well. . leaving this open, in case its of interest to others.

How to trick an OS X app into thinking the mouse is a finger?

I'm writing a Mac app that contains a collection view. This app is to be run on a large touchscreen display (55" EP series from Planar). Due to hardware limitation, the touchscreen doesn't send scroll events (or even any multitouch events). How can I go about tricking the app into thinking a "mousedown+drag" is the same as a "mousescroll"?
I got it working halfway by subclassing NSCollectionView and implementing my own NSPanGestureRecognizer handler in it. Unfortunately the result is clunky and doesn't have the feeling of a normal OS X scroll (i.e., the velocity effect at the end of a scroll, or scroll bounce at the ends of the content).
#implementation UCTouchScrollCollectionView
...
- (IBAction)showGestureForScrollGestureRecognizer:(NSPanGestureRecognizer *)recognizer
{
CGPoint location = [recognizer locationInView:self];
if (recognizer.state == NSGestureRecognizerStateBegan) {
touchStartPt = location;
startOrigin = [(NSClipView*)[self superview] documentVisibleRect].origin;
} else if (recognizer.state == NSGestureRecognizerStateEnded) {
/* Some notes here about a future feature: the Scroll Bounce
I don't want to have to reinvent the wheel here, but it
appears I already am. Crud.
1. when the touch ends, get the velocity in view
2. Using the velocity and a constant "deceleration" factor, you can determine
a. The time taken to decelerate to 0 velocity
b. the distance travelled in that time
3. If the final scroll point is out of bounds, update it.
4. set up an animation block to scroll the document to that point. Make sure it uses the proper easing to feel "natural".
5. make sure you retain a pointer or something to that animation so that a touch DURING the animation will cancel it (is this even possible?)
*/
[self.scrollDelegate.pointSmoother clearPoints];
refreshDelegateTriggered = NO;
} else if (recognizer.state == NSGestureRecognizerStateChanged) {
CGFloat dx = 0;
CGFloat dy = (startOrigin.y - self.scrollDelegate.scrollScaling * (location.y - touchStartPt.y));
NSPoint scrollPt = NSMakePoint(dx, dy);
[self.scrollDelegate.pointSmoother addPoint:scrollPt];
NSPoint smoothedPoint = [self.scrollDelegate.pointSmoother getSmoothedPoint];
[self scrollPoint:smoothedPoint];
CGFloat end = self.frame.size.height - self.superview.frame.size.height;
CGFloat threshold = self.superview.frame.size.height * kUCPullToRefreshScreenFactor;
if (smoothedPoint.y + threshold >= end &&
!refreshDelegateTriggered) {
NSLog(#"trigger pull to refresh");
refreshDelegateTriggered = YES;
[self.refreshDelegate scrollViewReachedBottom:self];
}
}
}
A note about this implementation: I put together scrollScaling and pointSmoother to try and improve the scroll UX. The touchscreen I'm using is IR-based and gets very jittery (especially when the sun is out).
In case it's relevant: I'm using Xcode 6 beta 6 (6A280e) on Yosemite beta (14A329r), and my build target is 10.10.
Thanks!
I managed to have some success using an NSPanGestureRecognizer and simulating the track-pad scroll wheel events. If you simulate them well you'll get the bounce from the NSScrollView 'for free'.
I don't have public code, but the best resource I found that explained what the NSScrollView expects is in the following unit test simulating a momentum scroll. (See mouseScrollByWithWheelAndMomentumPhases here).
https://github.com/WebKit/webkit/blob/master/LayoutTests/fast/scrolling/latching/scroll-iframe-in-overflow.html
The implementation of mouseScrollByWithWheelAndMomentumPhases gives some tips on how to synthesize the scroll events at a low level. One addition I found I needed was to actually set an incrementing timestamp in the event in order to get the scroll-view to play ball.
https://github.com/WebKit/webkit/blob/master/Tools/WebKitTestRunner/mac/EventSenderProxy.mm
Finally, in order to actually create the decaying velocity, I used a POPDecayAnimation and tweaked the velocity from the NSPanGestureRecognizer to feel similar. Its not perfect but it does stay true to NSScrollView's bounce.
I have a (dead) project on Github that does this with an NSTableView, so hopefully it will work well for an NSCollectionView.
Disclaimer: I wrote this while I was still learning GCD, so watch for retain cycles... I did not vet what I just posted for bugs. feel free to point any out :) I just tested this on Mac OS 10.9 and it does still work (originally written for 10.7 IIRC), not tested on 10.10.
This entire thing is a hack to be sure, it looks like it requires (seems to anyway) asynchronous UI manipulation (I think to prevent infinite recursion). There is probably a cleaner/better way and please share it when you discover it!
I havent touched this in months so I cant recall all the specifics, but the meat of it surely is in the NBBTableView code, which will paste snippets of.
first there is an NSAnimation subclass NBBScrollAnimation that handles the "rubber band" effect:
#implementation NBBScrollAnimation
#synthesize clipView;
#synthesize originPoint;
#synthesize targetPoint;
+ (NBBScrollAnimation*)scrollAnimationWithClipView:(NSClipView *)clipView
{
NBBScrollAnimation *animation = [[NBBScrollAnimation alloc] initWithDuration:0.6 animationCurve:NSAnimationEaseOut];
animation.clipView = clipView;
animation.originPoint = clipView.documentVisibleRect.origin;
animation.targetPoint = animation.originPoint;
return [animation autorelease];
}
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
typedef float (^MyAnimationCurveBlock)(float, float, float);
MyAnimationCurveBlock cubicEaseOut = ^ float (float t, float start, float end) {
t--;
return end*(t * t * t + 1) + start;
};
dispatch_sync(dispatch_get_main_queue(), ^{
NSPoint progressPoint = self.originPoint;
progressPoint.x += cubicEaseOut(progress, 0, self.targetPoint.x - self.originPoint.x);
progressPoint.y += cubicEaseOut(progress, 0, self.targetPoint.y - self.originPoint.y);
NSPoint constraint = [self.clipView constrainScrollPoint:progressPoint];
if (!NSEqualPoints(constraint, progressPoint)) {
// constraining the point and reassigning to target gives us the "rubber band" effect
self.targetPoint = constraint;
}
[self.clipView scrollToPoint:progressPoint];
[self.clipView.enclosingScrollView reflectScrolledClipView:self.clipView];
[self.clipView.enclosingScrollView displayIfNeeded];
});
}
#end
You should be able to use the animation on any control that has an NSClipView by setting it up like this _scrollAnimation = [[NBBScrollAnimation scrollAnimationWithClipView:(NSClipView*)[self superview]] retain];
The trick here is that the superview of an NSTableView is an NSClipView; I dont know about NSCollectionView, but I suspect that any scrollable control uses NSClipView.
Next here is how the NBBTableView subclass makes use of that animation though the mouse events:
- (void)mouseDown:(NSEvent *)theEvent
{
_scrollDelta = 0.0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
if (_scrollAnimation && _scrollAnimation.isAnimating) {
[_scrollAnimation stopAnimation];
}
});
}
- (void)mouseUp:(NSEvent *)theEvent
{
if (_scrollDelta) {
[super mouseUp:theEvent];
// reset the scroll animation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSClipView* cv = (NSClipView*)[self superview];
NSPoint newPoint = NSMakePoint(0.0, ([cv documentVisibleRect].origin.y - _scrollDelta));
NBBScrollAnimation* anim = (NBBScrollAnimation*)_scrollAnimation;
[anim setCurrentProgress:0.0];
anim.targetPoint = newPoint;
[anim startAnimation];
});
} else {
[super mouseDown:theEvent];
}
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSClipView* clipView=(NSClipView*)[self superview];
NSPoint newPoint = NSMakePoint(0.0, ([clipView documentVisibleRect].origin.y - [theEvent deltaY]));
CGFloat limit = self.frame.size.height;
if (newPoint.y >= limit) {
newPoint.y = limit - 1.0;
} else if (newPoint.y <= limit * -1) {
newPoint.y = (limit * -1) + 1;
}
// do NOT constrain the point here. we want to "rubber band"
[clipView scrollToPoint:newPoint];
[[self enclosingScrollView] reflectScrolledClipView:clipView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NBBScrollAnimation* anim = (NBBScrollAnimation*)_scrollAnimation;
anim.originPoint = newPoint;
});
// because we have to animate asyncronously, we must save the target value to use later
// instead of setting it in the animation here
_scrollDelta = [theEvent deltaY] * 3.5;
}
- (BOOL)autoscroll:(NSEvent *)theEvent
{
return NO;
}
I think that autoscroll override is essential for good behavior.
The entire code is on my github page, and it contains several other "touch screen" emulation tidbits, if you are interested, such as a simulation for the iOS springboard arrangeable icons (complete with "wiggle" animation using NSButtons.
Hope this helps :)
Edit: It appears that constrainScrollPoint: is deprecated in OS X 10.9. However, It should fairly trivial to reimplement as a category or something. Maybe you can adapt a solution from this SO question.

iOS6 UICollectionView and UIPageControl - How to get visible cell?

While studying iOS6 new features I got a question about UICollectionView.
I am currently testing it with Flow layout and the scroll direction set to horizontal, scrolling and paging enabled. I've set its size to exactly the same as my custom's cells, so it can show one at a time, and by scrollig it sideways, the user would see the other existing cells.
It works perfectly.
Now I want to add and UIPageControl to the collection view I made, so it can show up which cell is visible and how many cells are there.
Building up the page control was rather simple, frame and numberOfPages defined.
The problem I am having, as the question titles marks, is how to get which cell is currently visible in the collection view, so it can change the currentPage of the page control.
I've tried delegate methods, like cellForItemAtIndexPath, but it is made to load cells, not show them. didEndDisplayingCell triggers when a cell its not displayed anymore, the opposite event of what I need.
Its seems that -visibleCells and -indexPathsForVisibleItems, collection view methods, are the correct choice for me, but I bumped into another problem. When to trigger them?
Thanks in advance, hope I made myself clear enough so you guys can understand me!
You must setup yourself as UIScrollViewDelegate and implement the scrollViewDidEndDecelerating:method like so:
Objective-C
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGFloat pageWidth = self.collectionView.frame.size.width;
self.pageControl.currentPage = self.collectionView.contentOffset.x / pageWidth;
}
Swift
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = self.collectionView.frame.size.width
pageControl.currentPage = Int(self.collectionView.contentOffset.x / pageWidth)
}
I struggled with this for a while as well, then I was advised to check out the parent classes of UICollectionView. One of them happens to be UIScrollView and if you set yourself up as a UIScrollViewDelegate, you get access to very helpful methods such as scrollViewDidEndDecelerating, a great place to update the UIPageControl.
I would recommend a little tuned calculation and handling as it will update page control immediately in any scroll position with better accuracy.
The solution below works with any scroll view or it subclass (UITableView UICollectionView and others)
in viewDidLoad method write this
scrollView.delegate = self
then use code for your language:
Swift 3
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let pageWidth = scrollView.frame.width
pageControl.currentPage = Int((scrollView.contentOffset.x + pageWidth / 2) / pageWidth)
}
Swift 2:
func scrollViewDidScroll(scrollView: UIScrollView)
{
let pageWidth = CGRectGetWidth(scrollView.frame)
pageControl.currentPage = Int((scrollView.contentOffset.x + pageWidth / 2) / pageWidth)
}
Objective C
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat pageWidth = self.collectionView.frame.size.width;
self.pageControl.currentPage = (self.collectionView.contentOffset.x + pageWidth / 2) / pageWidth;
}
Another option with less code is to use visible item index path and set the page control.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.pageControl.currentPage = [[[self.collectionView indexPathsForVisibleItems] firstObject] row];
}
Place PageControl in your view or set by Code.
Set UIScrollViewDelegate
In Collectionview-> cellForItemAtIndexPath (Method) add the below
code for calculate the Number of pages,
int pages
=floor(ImageCollectionView.contentSize.width/ImageCollectionView.frame.size.width);
[pageControl setNumberOfPages:pages];
Add the ScrollView Delegate method,
pragma mark - UIScrollVewDelegate for UIPageControl
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGFloat pageWidth = ImageCollectionView.frame.size.width;
float currentPage = ImageCollectionView.contentOffset.x / pageWidth;
if (0.0f != fmodf(currentPage, 1.0f))
{
pageControl.currentPage = currentPage + 1;
}
else
{
pageControl.currentPage = currentPage;
}
NSLog(#"finishPage: %ld", (long)pageControl.currentPage);
}
I know this is an old one but I've just needed to implement this sort of feature again and have a bit to add which gives a more complete answer.
Firstly: Using scrollViewDidEndDecelerating assumes that the user lifted their finger while dragging (more like a flick action) and therefore there is a deceleration phase. If the user drags without lifting the finger the UIPageControl will still indicate the old page from before the drag began. Instead using the scrollViewDidScroll callback means that the view is updated both after dragging and flicking and also during dragging and scrolling so it feels much more responsive and accurate for the user.
Secondly: Relying on the pagewidth for calculating the selected index assumes all the cells have the same width and that there is one cell per screen. taking advantage of the indexPathForItemAtPoint method on UICollectionView gives a more resilient result which will work for different layouts and cell sizes. The implementation below assumes the centre of the frame is the desired cell to be represented in the pagecontrol. Also if there are intercell spacings there will times during scrolling when the selectedIndex could be nil or optional so this needs to be checked and unwrapped before setting on the pageControl.
func scrollViewDidScroll(scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset
let centrePoint = CGPointMake(
contentOffset.x + CGRectGetMidX(scrollView.frame),
contentOffset.y + CGRectGetMidY(scrollView.frame)
)
if let index = self.collectionView.indexPathForItemAtPoint(centrePoint){
self.pageControl.currentPage = index.row
}
}
One more thing - set the number of pages on the UIPageControl with something like this:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
self.pageControl.numberOfPages = 20
return self.pageControl.numberOfPages
}
Simple Swift
public func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
pageControl.currentPage = (collectionView.indexPathsForVisibleItems().first?.row)!
}
UIScrollViewDelegate is already implemented if you implement UICollectionViewDelegate
If using scrollViewDidScroll, updating the page control should be done manually to ⚠️ avoid the flickering dots when you tap on the page control.
Setup the UIPageControl.
let pageControl = UIPageControl()
pageControl.pageIndicatorTintColor = .label
pageControl.defersCurrentPageDisplay = true // Opt-out from automatic display
pageControl.numberOfPages = viewModel.items.count
pageControl.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged)
Implement the action (using the extensions below).
#objc func pageControlValueChanged(_ sender: UIPageControl) {
collectionView.scroll(to: sender.currentPage)
}
Update UIPageControl manually on every scroll.
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
pageControl.currentPage = collectionView.currentPage
pageControl.updateCurrentPageDisplay() // Display only here
}
}
Convinient UICollectionView extensions.
extension CGRect {
var middle: CGPoint {
CGPoint(x: midX, y: midY)
}
}
extension UICollectionView {
var visibleArea: CGRect {
CGRect(origin: contentOffset, size: bounds.size)
}
var currentPage: Int {
indexPathForItem(at: visibleArea.middle)?.row ?? 0
}
func scroll(to page: Int) {
scrollToItem(
at: IndexPath(row: page, section: 0),
at: .centeredHorizontally,
animated: true
)
}
}

How to find out if scrollView is about to be scrolled up or down

I would like to find out if a scrollView is scrolled up or down. Ideally, I'd like to have only one call if the scrollView is scrolled up or down. I tried this but it will obviously not tell me anything about the direction:
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
NSLog(#"%.2f", scrollView.contentOffset.y);
}
contentOffset will always be 0 - it doesn't matter whether I scrolled up or down. Now I could simply check in -(void)scrollViewDidScroll: if the offset is positive or negative, but this is called constantly. scrollViewWillBeginDragging has the advantage of being called only once and this is what I need. Is there something like scrollViewDidBeginDragging? I didn't find anything in the docs. Any smart workaround?
Store the initial content offset in scrollViewWillBeginDragging:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
self.initialContentOffset = scrollView.contentOffset.y;
self.previousContentDelta = 0.f;
}
And check it on each scrollViewDidScroll:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat prevDelta = self.previousContentDelta;
CGFloat delta = scrollView.contentOffset.y - self.initialContentOffset;
if (delta > 0.f && prevDelta <= 0.f) {
// started scrolling positively
} else if (delta < 0.f && prevDelta >= 0.f) {
// started scrolling negatively
}
self.previousContentDelta = delta;
}
It IS possible to perform this check in scrollViewWillBeginDragging before any scrolling is registered. (IOS 5+). By examining the scroll view's built-in pan gesture recognizer you can check gesture direction.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
CGPoint translation = [scrollView.panGestureRecognizer translationInView:scrollView.superview];
if(translation.y > 0)
{
// react to dragging down
} else
{
// react to dragging up
}
}
I found it very useful in canceling out of a scroll at the very first drag move when the user is dragging in a forbidden direction.
Create a declared property to let us know that the tableview is starting to scroll. Let's use a BOOL called scrollViewJustStartedScrolling.
In scrollViewWillBeginDragging set it to true:
self.scrollViewJustStartedScrolling = YES;
In scrollViewDidScroll do something like:
if (self.scrollViewJustStartedScrolling) {
// check contentOffset and do what you need to do.
self.scrollViewJustStartedScrolling = NO;
}
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
if (velocity.y > 0){
NSLog(#"up");
} else {
NSLog(#"down");
}
}

UIScrollView moves image to the top left corner when zooming in

Looking at this question: Prevent UIScrollView from moving contents to top-left, i'm having the exact issue.
I'm using this tutorial: http://cocoadevblog.heroku.com/iphone-tutorial-uiimage-with-zooming-tapping-rotation
Back to the similar question, if i disable the UIScrollViewPanGestureRecognizer, i'm not able to pan the zoomed image anymore.
I have a UIImageView within a UIScrollView, and i want to be able to zoom and pan the image as well.
How can i do tho disable the contents moving to the top left corner when zooming in?
Seems i solved tweaking my UiScrollView Autosizing and Origin in the Size inspector \ Attributes inspector. I unchecked Paging Enabled and the magic happened.
Just in case anyone else comes here and none of the other answers seem to work ( which was my case ), what did the trick for me was setting the contentSize of the scrollView. Just set it to the size whatever subview you are zooming in on and it should work.
Make a subclass of UIScrollView, and add this method to it:
- (void)layoutSubviews {
[super layoutSubviews];
// center the image as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
//get the subView that is being zoomed
UIView *subView = [self.delegate viewForZoomingInScrollView:self];
if(subView)
{
CGRect frameToCenter = subView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
subView.frame = frameToCenter;
}
else
NSLog(#"No subView set for zooming in delegate");
}
If I understand you right, you want to allowing scrolling only when theImageView is zoomed in, then a scrollView.zoomScale > 1. For my app requirement I am using this.
Add UIScrollView's delegate method as follows and check.
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
{
CGFloat offsetY = 0;
if (aScrollView.zoomScale > 1)
offsetY = aScrollView.contentOffset.y;
[aScrollView setContentOffset: CGPointMake(aScrollView.contentOffset.x, offsetY)];
}