For my current project I need to move rows in a UITableView (without being in an edit mode) simply by tap and hold an appropriate row - like in the Clear app.

After struggling a few hours I came up with a solution by creating a UITableView subclass that I’d like to share. So I’ve created a sample project, added some (hopefully useful) comments and pushed it into a github Repository.
I’ve put it under BSD license, so feel free to use it in your project. Please contact me (@FlorianMielke), if you have any suggestions for improving the code.
Take care,
Florian
Last week the new Path 2 app was released with a bunch of great UI tweaks, which I promise we will definitely see in other apps really soon.
One of the most impressive one to me was the info panel attached to the scroll view indicator which appears as you begin scrolling and disappears after the scroll view stops.

In contrast to the existing table view sections the info panel approach is non-persistent. It allows you to temporarily group data in a table view. Therefore it is not qualified to simply replace every table view section in your app, but it could be a good alternative or even an addition in some cases. So let’s dive into one possible solution I’ve figured out how to create an attached info panel.
A UIScrollViewIndicator is the top most subview of a UIScrollView or of one of it’s subclasses, e.g. a UITableView. The indicator itself is simply an UIImageView. Unless you are not scrolling it’s alpha is 0 and therefore not visible - but it’s there right from the beginning. When the user scrolls to the top or bottom and drags it’s finger a little further – like a rubber band - the size of the indicator shrinks until it finally became a dot.
The basic idea is to add the info panel’s root layer as a sublayer to the indicator’s root layer. With that, the info panel scrolls smoothly and stays in parallel to the indicator automatically. In the case were the scroll view hit the edges, we want the info panel to stay visible at the top or bottom of the screen, so we need to do some extra calculation to let it stay centered.
Here we just need four ivars which are quite self-explanatory:
#import <UIKit/UIKit.h> #import <Foundation/Foundation.h> #import <QuartzCore/QuartzCore.h> @interface FMInfoPanelViewController : UIViewController{ UIScrollView *scrollView; UIView *infoPanel; CGSize initialSizeOfInfoPanel; CGFloat initialHeightOfScrollIndicator; } @end
In our implementation we first create an UIImageView which simply acts as a background of our info panel. After that we create the info panel itself with a negative x-origin so it will appear left to the scroll indicator.
#pragma mark -
#pragma mark View lifecycle
- (void)viewDidLoad
{
CGRect viewFrame = [[self view] frame];
scrollView = [[UIScrollView alloc] initWithFrame:viewFrame];
[scrollView setDelegate:self];
[scrollView setContentSize:CGSizeMake(viewFrame.size.width, viewFrame.size.height * 4)];
[[self view] addSubview:scrollView];
UIImageView *backgroundImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"infoPanel.png"]];
initialSizeOfInfoPanel = [backgroundImage frame].size;
infoPanel = [[UIView alloc] initWithFrame:CGRectMake(initialSizeOfInfoPanel.width * (-1), 0, initialSizeOfInfoPanel.width, initialSizeOfInfoPanel.height)];
[infoPanel addSubview:backgroundImage];
[backgroundImage release], backgroundImage = nil;
}
scrollViewWillBeginDragging: When the user starts scrolling we check whether the info panel is currently visible or not. If not we will add it’s root layer as as sublayer of the scroll indicators root layer. One important thing I’ve figured out is to set the background color of our info panels layer to the same as the scroll indicator’s root layer. Otherwise the scroll indicator wont be translucent anymore.
#pragma mark -
#pragma mark Scroll view delegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)aScrollView
{
if (![infoPanel superview])
{
UIView *indicator = [[aScrollView subviews] lastObject];
CGRect indicatorFrame = [indicator frame];
initialHeightOfScrollIndicator = indicatorFrame.size.height;
[[infoPanel layer] setBackgroundColor:[[indicator layer] backgroundColor]];
[[indicator layer] addSublayer:[infoPanel layer]];
// Center the info panel
CGRect infoPanelFrame = [infoPanel frame];
infoPanelFrame.size = initialSizeOfInfoPanel;
infoPanelFrame.origin.y = indicatorFrame.size.height / 2 - infoPanelFrame.size.height / 2;
[infoPanel setFrame:CGRectIntegral(infoPanelFrame)];
}
}
scrollViewDidScroll: As our info panel is already attached to the scroll indicator we can avoid a lot of unnecessary calculations when the scroll view just scrolls within it’s content area. Though we have to do some work when we reach the top or bottom of the screen:
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
{
UIView *indicator = [[aScrollView subviews] lastObject];
CGRect indicatorFrame = [indicator frame];
// We are somewhere at the edge (top or bottom)
if (indicatorFrame.size.height < initialHeightOfScrollIndicator)
{
CGRect infoPanelFrame = [infoPanel frame];
// The indicator starts shrinking, so we need to adjust our info panel's y-origin to stays centered
if (indicatorFrame.size.height > infoPanelFrame.size.height + 2)
{
infoPanelFrame.origin.y = (indicatorFrame.size.height / 2) - (infoPanelFrame.size.height / 2);
}
// We are at the bottom of the screen and the indicator is now smaller than our info panel
else if (indicatorFrame.origin.y > 0)
{
infoPanelFrame.origin.y = (infoPanelFrame.size.height - indicatorFrame.size.height) * (-1);
}
[infoPanel setFrame:infoPanelFrame];
}
}
scrollViewDidEndScrollingAnimation: As we are good citizens we will remove our info panel when the scroll view stops scrolling.
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[infoPanel removeFromSuperview];
}
The Path 2 app has some nice additional animations to fade in and fade out the info panel. It also stays visible for a second after the scroll view has stopped scrolling so the user knows if he scrolled to the right position.
I’ve created a public github gist and Xcode sample project so you can download the source files.
I hope this solution gives you an idea of how to implement this great UI approach to your app to increase the user experience. If you have any suggestions on how to solve it better, more easily or in a completely different way, please contact me @FlorianMielke.
Take care,
Florian
After months of development and many ups and downs during that time — you have no idea how complex time tracking is — I’m very excited to announce the start of the WorkTimes 3.0 test flight today.

WorkTimes 3.0 is the result of over one year of experiences being in the App Store and countless feedbacks from customers. It is definitely the most extensive update since Version 1.0.
Here is a small abstract of the top new features in WorkTimes 3.0:
And there are still a lot of features waiting for the upcoming versions.
Please fill out our TestFlight Form to be added to the beta list. The first version will be available till the end of this week.
Please only subscribe if you are willed to intensively test all beta versions and provide comprehensive feedback to beta@madefm.com.
The beta version will be provided as a completely separate install. None of your productive data and installed version will be affected.
Happy testing,
Florian