Question

The music app on the iPad has quite a lot of interesting custom UI features, one of which is the table view displayed when looking at the albums of a particular artist.

It has images along the side of each section, almost acting in a very similar fashion to section header views. If I scroll down, the top image stays there until the next image comes along to bump it off, and vice versa.

Initially it seems quite impossible without reimplementing the UITableView all over again, but I think it's semi-doable by simply subclassing a UITableView to draw these section images on the side, outside of the uitableview, whenever it calls layoutSubviews (i.e. whenever it scrolls). There are slight problems with this: because it draws outside of the UITableView, any autoresizing will probably throw the entire layout off. And I'm really just unsure about the feasibility of it all.

Any thoughts?


Edit: I just realized that my first idea wouldn't work in that any touches on the images side themselves wouldn't be tracked by the tableview, so you couldn't scroll if you touched there. I think the biggest issue here is figuring out how to propagate the touches so that touches are tracked across the entire view.

Was it helpful?

Solution 2

After thinking about it randomly in the middle of the night, I finally figured out a real solution.

Embed the UITableview in a UIScrollView, and when the user scrolls the view, the touches go through through the parent scrollview, and not the tableview itself. Pretty much everything you need to do lies in the layoutSubviews method; since layoutSubviews of the scrollview is called whenever the user scrolls the view, just change the frame of the tableview to stay fixed onscreen, and change the tableview's contentOffset to match that of the scrollview.

Example code:

- (void) layoutSubviews{
    tableView.frame = CGRectMake(self.contentOffset.x, self.contentOffset.y, tableView.frame.size.width, tableView.frame.size.height);
    tableView.contentOffset = self.contentOffset;   
}

At this point, you can adjust the tableview to one side, and do whatever you want on the other.

OTHER TIPS

you could check what section you are in in the tableview (or the topmost etc) somehow, then simply have a UIView that gets updated whenever the section changes, my idea is a bit hard to explain, but it wouldn't have to do with redoing UITableView at all, just have another view beside the table view to do the images instead of trying to embed that functionality.

Another way to do this, is perhaps a bit more tricky but you could try to indent your cells so they dont start at the very left of the table view, make the tableview very wide and have a custom view in the section header that contains that image on its left and hopefully would do it correctly? good question and a fun one to play with.

Okay I have done something similar to what the music app and spotlight search does.

I have not subclassed UITableView, I have just tracked the sections through it's scrollViewDidScroll method, and added the header views to the left of the tableView (so you will have to put the tableview to the right in your viewController's view, which means you can't use UITableViewController).

this method should be called in the scrollViewDidScroll, ViewDidLoad, and in didRotateFromInterfaceOrientation: (if you support rotation)

keep in mind that you will have to make space in the left equal to the size of the headers in any interface orientation that you support.

-(void)updateHeadersLocation
{
for (int sectionNumber = 0; sectionNumber != [self numberOfSectionsInTableView:self.tableView]; sectionNumber++)
{
    // get the rect of the section from the tableview, and convert it to it's superview's coordinates
    CGRect rect = [self.tableView convertRect:[self.tableView rectForSection:sectionNumber] toView:[self.tableView superview]];

    // get the intersection between the section's rect and the view's rect, this will help in knowing what portion of the section is showing
    CGRect intersection = CGRectIntersection(rect, self.tableView.frame);

    CGRect viewFrame = CGRectZero; // we will start off with zero

    viewFrame.size = [self headerSize]; // let's set the size

    viewFrame.origin.x = [self headerXOrigin];

    /*

     three cases:

     1. the section's origin is still showing -> header view will follow the origin
     2. the section's origin isn't showing but some part of the section still shows -> header view will stick to the top
     3. the part of the section that's showing is not sufficient for the view's height -> will move the header view up

     */

    if (rect.origin.y >= self.tableView.frame.origin.y)
    {
        // case 1
        viewFrame.origin.y = rect.origin.y;
    }
    else
    {
        if (intersection.size.height >= viewFrame.size.height)
        {
            // case 2
            viewFrame.origin.y = self.tableView.frame.origin.y;
        }
        else
        {
            // case 3
            viewFrame.origin.y = self.tableView.frame.origin.y + intersection.size.height - viewFrame.size.height;
        }
    }

    UIView* view = [self.headerViewsDictionary objectForKey:[NSString stringWithFormat:@"%i", sectionNumber]];

    // check if the header view is needed
    if (intersection.size.height == 0)
    {
        // not needed, remove it
        if (view)
        {
            [view removeFromSuperview];
            [self.headerViewsDictionary removeObjectForKey:[NSString stringWithFormat:@"%i", sectionNumber]];
            view = nil;
        }
    }
    else if(!view)
    {
        // needed, but not available, create it and add it as a subview

        view = [self headerViewForSection:sectionNumber];

        if (!self.headerViewsDictionary && view)
            self.headerViewsDictionary = [NSMutableDictionary dictionary];

        if (view)
        {
            [self.headerViewsDictionary setValue:view forKey:[NSString stringWithFormat:@"%i", sectionNumber]];

            [self.view addSubview:view];
        }
    }


    [view setFrame:viewFrame];
}
}

also we need to declare a property that would keep the views that are visible:

@property (nonatomic, strong) NSMutableDictionary* headerViewsDictionary;

these methods return the size and X axis offset of the header views:

-(CGSize)headerSize
{
return CGSizeMake(44.0f, 44.0f);
}

-(CGFloat)headerXOrigin
{
return 10.0f;
} 

I have Built the code so that any header view that's not needed gets removed, so we need a method that would return the view whenever needed:

-(UIView*)headerViewForSection:(NSInteger)index
{
UIImageView* view = [[UIImageView alloc] init];

if (index % 2)
{
    [view setImage:[UIImage imageNamed:@"call"]];
}
else
{
    [view setImage:[UIImage imageNamed:@"mail"]];
}

return view;
}

here's how it will look :

the section

the section

How it will look in lanscape, I have used contraints to give 44px in the left of the tableView

in landscape orientation hope this helps :).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top