开发者

stopping a UIScrollView at a specific position

Im trying to implement a horizontal scroll view for an iPad app. The scroll view shows a series of images. After scrolling, when the scroll view stops I want the images to be aligned along specific points along the horizontal. To do this I tried 2 approaches:

1) Disable scrolling for the UIScrollView and instead implement a PanGestureRecogniser which would detect the translation and velocity of the "swipe/drag" motion and set the content offset of the scroll view accordingly to the required pre-determined points. But, the problem here is that i开发者_运维知识库f the user slowly drags the scroll view, the scroll view motion is very jerky because the retrieved x co-ordinated of the scroll view jump back and forth around the point the finger is pressed. For eg: x values goes from 50 to 70 when the finger moves along 60-70. How can i make the motion of the scroll view smooth?

2) Enable scrolling for the UIScrollView and check the end of deceleration and then set the content offset of the scroll view to adjust it to the necessary position. But the problem here is that the scroll view adjusts to its final position only after it stops decelerating and there is a visible delay and this animation does not look good too. Is there a way that i can detect when the scroll view is about to stop scrolling and then adjust its position instead of waiting it to end scrolling. From what iv read on some forums this wont be possible.

3) A paging scrollview will not work in this scenario as I dont want the entire width of images to be scrolled out.

Does anyone have any ideas on how I can implement the above functionality.


I've faced the same problem with a vertical scrollview and used the approach 2. Even if this question is old, I didn't find a solution here and felt it would be great to answer.

The short answer is the scrollViewWillEndDragging:withVelocity:targetContentOffset: method in UIScrollViewDelegate protocol.

It gives you the targetContentOffset when the user finishes scrolling and the animation start decelerating. You can change the targetContentOffset in this method, so the animation will end exactly where you can. Your job is only to calculate the target position.

Here's the UIView subclass I've created. I wanted to create a UIPickerView like class, but I wanted it to stop on specific positions (like the date picker).

I created an UIView subclass that conformed to UIScrollViewDelegate. In this UIView, I did add a scrollView. This scrollView will contain UILabel instances. Be careful, I use ARC, so I do not release anything.

#import <UIKit/UIKit.h>

@protocol MyPickerViewDataSource <NSObject>

-(NSUInteger)numberOfRowsForSender:(id)sender;
-(NSString *)titleForRowAtIndex:(NSUInteger)rowIndex sender:(id)sender;

@end

@interface MyPickerView : UIView <UIScrollViewDelegate>

@property (weak, nonatomic) id<MyPickerViewDataSource> dataSource;

@end

The implementation :

#import "MyPickerView.h"

#define CONTENT_ROW_HEIGHT 30

typedef enum {
    ScrollDirectionUp,
    ScrollDirectionDown
} ScrollDirection;

@interface MyPickerView() {

    int topOffset;
    int bottomOffset;
    int lastContentOffset;
    int targetLabelIndex;
    ScrollDirection scrollDirection;
}

@implementation PickerView

- (void)initSetup {
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(self.frame.origin.x,self.frame.origin.y, self.frame.size.width, self.frame.size.height)];
self.scrollView.delegate = self;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.userInteractionEnabled = YES;
[self addSubview:self.scrollView];

targetLabelIndex = 0;

topOffset = (self.scrollView.frame.size.height/2)-CONTENT_ROW_HEIGHT/2;
bottomOffset = topOffset;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if(self) {
        [self initSetup];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if(self) {
        [self initSetup];
    }
    return self;
}

- (void)layoutSubviews {

    [super layoutSubviews];

    NSUInteger numberOfRows = [self.dataSource numberOfRowsForSender:self];

    self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, topOffset+numberOfRows*CONTENT_ROW_HEIGHT+bottomOffset);

    for (int index = 0; index < numberOfRows; index++)
    {
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(self.scrollView.frame.origin.x,
                                                                   topOffset+index*CONTENT_ROW_HEIGHT,
                                                                   self.scrollView.frame.size.width,
                                                                   CONTENT_ROW_HEIGHT)];

        label.backgroundColor = [UIColor clearColor];
        label.textColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
        label.text = [self.dataSource titleForRowAtIndex:index sender:self];

        [self.scrollView addSubview:label];
    }

    [(UILabel *)[self.scrollView.subviews objectAtIndex:targetLabelIndex] setTextColor:[UIColor blueColor]];
}

- (void)reloadData {

    NSUInteger numberOfRows = [self.dataSource numberOfRowsForSender:self];

    self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, numberOfRows*CONTENT_ROW_HEIGHT);

    [self.scrollView setContentOffset:CGPointMake(0.0, 0.0) animated:NO];

    [self layoutSubviews];
}

#pragma mark - UIScrollViewDelegate

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    if(lastContentOffset>self.scrollView.contentOffset.y) {

        scrollDirection = ScrollDirectionUp;
    }
    else {

        scrollDirection = ScrollDirectionDown;
    }

    lastContentOffset = self.scrollView.contentOffset.y;
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {

    targetLabelIndex = (targetContentOffset->y/CONTENT_ROW_HEIGHT)+1;

    if(scrollDirection==ScrollDirectionUp && targetLabelIndex>0) {

        targetLabelIndex--;
    }

    targetContentOffset->y = targetLabelIndex*CONTENT_ROW_HEIGHT;

    [(UILabel *)[self.scrollView.subviews objectAtIndex:targetLabelIndex] setTextColor:[UIColor blueColor]];
}

@end


You can actually still use a paging scroll view to do this. All you need to do is set clipsToBounds to NO (so that the contents in the scroll view are allowed to render outside its frame), and then set the frame to the desired alignment steps.

For example, if you want to align the scroll view on multiples of 200, you could do this:

scrollView.pagingEnabled = YES;
scrollView.clipsToBounds = NO;

// horizontally center scroll view with a width of 200 and the parent's height
scrollView.frame = CGRectMake(
    floor((parentView.bounds.size.width - 200) / 2),
    0,
    200,
    parentView.bounds.size.height
);

[parentView addSubview:scrollView];
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜