Iphone – Horizontal UIScrollView inside a UITableViewCell

iphonescrolluiscrollviewuitableview

I'm trying to create the exact same effect as in the AppStore app for viewing screenshots: inside a UITableView, I have custom subclasses of UITableViewCell's. One of them is designed to show previews of some product, say 4 images. I want them to show the same way as the AppStore presents screenshots of an app : inside a horizontal UIScrollView with its attached UIPageControl.

So I add my UIScrollView and my UIPageControl to my UITableViewCell, just like that :

@implementation phVolumePreviewCell
- (id)init {
    if (self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kVolumePreviewCellIdentifier]) {
        [self setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
        [self setSelectionStyle:UITableViewCellSeparatorStyleNone];

        previews = [[NSMutableArray alloc] initWithCapacity:4];
        for (int i = 0; i < 4; i++) {
            [previews addObject:@"dummy"];
        }

        scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero];
        [scrollView setPagingEnabled:YES];
        [scrollView setBackgroundColor:[UIColor grayColor]];
        [scrollView setIndicatorStyle:UIScrollViewIndicatorStyleBlack];
        [scrollView setShowsHorizontalScrollIndicator:YES];
        [scrollView setBounces:YES];
        [scrollView setScrollEnabled:YES];
        [scrollView setDelegate:self];
        [self.contentView addSubview:scrollView];
        [scrollView release];

        pageControl = [[UIPageControl alloc] initWithFrame:CGRectZero];
        [pageControl setNumberOfPages:[previews count]];
        [pageControl setBackgroundColor:[UIColor grayColor]];
        [self.contentView addSubview:pageControl];
        [pageControl release];
    }
    return self;
}

(note: I'm populating "previews" with dummy data here, just in order to have a [previews count] that works ; I want to view the scrollView's scroll indicator for test purposes only, I'll hide them later on).

My problem is that I can scroll the whole UITableView containing this phVolumePreviewCell (subclass of UITableViewCell), but I can't scroll the UIScrollView. How can I achieve this?

The only information I found is there: http://theexciter.com/articles/touches-and-uiscrollview-inside-a-uitableview.html
But it talks of old SDKs (namely 2.2) and I'm sure there is a more "recent" approach to doing this.

Some precisions :

  • I can scroll the UIScrollView with two fingers. That's not what I want, I want it to be able to scroll with one finger only.
  • When I scroll with two fingers, the TableView still intercepts the touches and scroll a little bit to the top or the bottom. I'd like the TableView to stick at its position when I touch the ScrollView.

I believe I have to work out how to intercept touches on my TableView, and if the touch is on the phVolumePreviewCell (custom UITableViewCell subclass), pass it to the cell instead of handling it on the TableView.

For the record, my TableView is created programmatically in a UITableViewController subclass:

@interface phVolumeController : UITableViewController <UITableViewDelegate> {
    LocalLibrary *lib;
        NSString *volumeID;
        LLVolume *volume;
}

- (id)initWithLibrary:(LocalLibrary *)newLib andVolumeID:(NSString *)newVolumeID;
@end

 

@implementation phVolumeController

#pragma mark -
#pragma mark Initialization

- (id)initWithLibrary:(LocalLibrary *)newLib andVolumeID:(NSString *)newVolumeID {
    if (self = [super initWithStyle:UITableViewStylePlain]) {
        lib          = [newLib retain];
        volumeID     = newVolumeID;
        volume       = [(LLVolume *)[LLVolume alloc] initFromLibrary:lib forID:volumeID];
        [[self navigationItem] setTitle:[volume title]];
    }
    return self;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kVolumePreviewCellIdentifier];
    if (cell == nil) {
        cell = [[[phVolumePreviewCell alloc] init] autorelease];
    }

    [(phVolumePreviewCell *)cell setVolume:volume];
    return (UITableViewCell *)cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return kVolumePreviewCellHeight;
}

Thanks for any help!

Best Answer

I have found a workaround.

Recreating from scratch a very simple UITableView (8 rows), and inserting a UIScrollView in just the second row, works perfectly. Nested scroll views (the TableView and the ScrollView) scroll independently as expected. All that was done in a single-file test project, in the AppDelegate.

So... first I thought "this might be a delegation problem", so I told the scrollView that its delegate was the TableView, not my custom TableViewCell subclass. To no avail.

Then instead of resorting to a custom, clean TableViewCell subclass that just adds the ScrollView and the PageControl, I resorted to creating a plain TableViewCell in the TableView's cellForRowAtIndexPath: method. Then, I create a UIScrollView right after initializing the TableViewCell, then add it to the TableViewCell, and shazam... it works!

Before:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kVolumePreviewCellIdentifier];
if (cell == nil) {
    cell = [[[phVolumePreviewCell alloc] init] autorelease];
}

[(phVolumePreviewCell *)cell setVolume:volume];
// That does the job of creating the cell's scrollView and pageControl

return (UITableViewCell *)cell

After:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kvcPreviewRowIdentifier];
if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kvcPreviewRowIdentifier] autorelease];

    previewScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, kvcPreviewScrollViewHeight)];
    [previewScrollView setContentSize:CGSizeMake(1000, kvcPreviewScrollViewHeight)];
    [[cell contentView] addSubview:previewScrollView];

    previewPageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, kvcPreviewScrollViewHeight, tableView.frame.size.width, kvcPreviewPageControlHeight)];
    [previewPageControl setNumberOfPages:4];
    [[cell contentView] addSubview:previewPageControl];
}

return (UITableViewCell *)cell;

How the heck comes that when I create my scrollView from the TVCell subclass, it doesn't play nice ; but when I create the scrollView from the TableView right after creating a "classical" TVCell, it works?

I might have found a solution, but I'm still puzzled by the reason.

Related Topic