Hidden menu Part 2: Following the finger
2012-09-24
Updated 2025-12-31 · dc272e6
In Part 1 of Creating a hidden menu in iOS I showed how to add gestures to handle the hide/reveal functionality. The swipe gesture is a great and gives a good impression , but it can be better. It is much nicer to have the effect that the *content *View is following the users finger , and that is exactly what we are going to do here.
Lets start with the project form the previous tutorial , if you dont have it , you can pick it up here.
First we start by commenting out/removing the GestureRecognizer code in the viewDidLoad , we need to get rid of it because the touch events could conflict with them.
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *menuPlistPath = [[NSBundle mainBundle] pathForResource:@"Menu" ofType:@"plist"];
menuArray = [[NSArray alloc] initWithContentsOfFile:menuPlistPath];
}
Now lets jump to the touch events ,this requires no additional setup,all we have to do is implement the delegate methods.
The first one we will handle is *touchesBegan *. All we will be doing here is storing the x coordinate of where the touch began ,this is used later to calculate how we should move the contents view.
float difference;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint contentTouchPoint = [[touches anyObject] locationInView:content];
difference = contentTouchPoint.x;
}
Next up is touchesMoved ,here we get the coordinate of the touch in self.view ,then based on the coordinate ,calculate a new x coordinate for the *content *view. Here we also implement a check to make sure that the movement does not exceed the bounds of *menuTable. *After we have calculated *xTarget , *All thats left to do is animate the movement.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint pointInView = [[touches anyObject] locationInView:self.view];
float xTarget = pointInView.x - difference;
if(xTarget > menuTable.frame.size.width)
xTarget = menuTable.frame.size.width;
else if( xTarget < 0)
xTarget = 0;
[UIView animateWithDuration:.25
animations:^{
[content setFrame:CGRectMake(xTarget, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}
];
}
The final method we need to handle is touchesEnded. This event is fired when the users finger leaves the screen , at this point the content view is most likely to be in mid transition between the "menu shown" and "menu hidden" states,so here we need to calculate which of those states it "snaps" to:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint endPoint = [[touches anyObject] locationInView:self.view];
float xTarget = endPoint.x - difference;
if(xTarget < (menuTable.frame.size.width/2))
xTarget = 0;
else
xTarget = menuTable.frame.size.width;
[UIView animateWithDuration:.25
animations:^{
[content setFrame:CGRectMake(xTarget, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}
];
}
And thats a wrap , now if you run the project you will be able to drag the menu into hidden/shown states and it will actually follow your finger and not just be fired by a swipe. Below is the full modified code of HiddenMenu:
ViewController.h:
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *menuTable;
@property (weak, nonatomic) IBOutlet UIView *content;
@property (weak, nonatomic) IBOutlet UINavigationBar *contentNavBar;
- (IBAction)showMenuDown:(id)sender;
@end
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (strong,nonatomic) NSArray *menuArray; -(void)showMenu;
-(void)hideMenu;
@end
@implementation ViewController
@synthesize menuArray,
menuTable,
content,
contentNavBar;
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *menuPlistPath = [[NSBundle mainBundle] pathForResource:@"Menu" ofType:@"plist"];
menuArray = [[NSArray alloc] initWithContentsOfFile:menuPlistPath];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - UITableView Datasource -
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return menuArray.count;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MenuCell"];
NSDictionary *menuItem = [menuArray objectAtIndex:indexPath.row];
cell.textLabel.text = menuItem[@"Title"];
cell.detailTextLabel.text = menuItem[@"Subtitle"];
return cell;
}
#pragma mark - UITableView Delegate -
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
contentNavBar.topItem.title = menuArray[indexPath.row][@"Title"];
}
#pragma mark - Actions -
- (IBAction)showMenuDown:(id)sender {
if(content.frame.origin.x == 0) [self showMenu];
else
[self hideMenu];
}
#pragma mark - animations -
-(void)showMenu{
[UIView animateWithDuration:.25
animations:^{
[content setFrame:CGRectMake(menuTable.frame.size.width, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}
];
}
-(void)hideMenu{
[UIView animateWithDuration:.25
animations:^{
[content setFrame:CGRectMake(0, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}
];
}
#pragma mark - Gesture handlers -
-(void)handleSwipeLeft:(UISwipeGestureRecognizer*)recognizer{
}
-(void)handleSwipeRight:(UISwipeGestureRecognizer*)recognizer{
}
#pragma mark - Touch events -
float difference;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint contentTouchPoint = [[touches anyObject] locationInView:content];
difference = contentTouchPoint.x;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint pointInView = [[touches anyObject] locationInView:self.view];
float xTarget = pointInView.x - difference;
if(xTarget > menuTable.frame.size.width)
xTarget = menuTable.frame.size.width;
else if( xTarget < 0)
xTarget = 0;
[UIView animateWithDuration:.25
animations:^{
[content setFrame:CGRectMake(xTarget, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}
];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint endPoint = [[touches anyObject] locationInView:self.view];
float xTarget = endPoint.x - difference;
if(xTarget < (menuTable.frame.size.width/2))
xTarget = 0;
else
xTarget = menuTable.frame.size.width;
[UIView animateWithDuration:.25
animations:^{
[content setFrame:CGRectMake(xTarget, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}
];
}
@end
Originally posted on Blogspot