├── LICENSE ├── PulldownMenu.h ├── PulldownMenu.m └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Bernard Gatt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PulldownMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // PulldownMenu.h 3 | // 4 | // Created by Bernard Gatt 5 | // 6 | 7 | #import 8 | 9 | @protocol PulldownMenuDelegate 10 | -(void)menuItemSelected:(NSIndexPath *)indexPath; 11 | -(void)pullDownAnimated:(BOOL)open; 12 | @end 13 | 14 | @interface PulldownMenu : UIView { 15 | UITableView *menuList; 16 | NSMutableArray *menuItems; 17 | 18 | UIView *handle; 19 | UIView *masterView; 20 | UIPanGestureRecognizer *navigationDragGestureRecognizer; 21 | UIPanGestureRecognizer *handleDragGestureRecognizer; 22 | UINavigationController *masterNavigationController; 23 | UIDeviceOrientation currentOrientation; 24 | 25 | float topMargin; 26 | float tableHeight; 27 | } 28 | 29 | @property (nonatomic, assign) id delegate; 30 | @property (nonatomic, retain) UITableView *menuList; 31 | @property (nonatomic, retain) UIView *handle; 32 | 33 | /* Appearance Properties */ 34 | @property (nonatomic) float handleHeight; 35 | @property (nonatomic) float animationDuration; 36 | @property (nonatomic) float topMarginPortrait; 37 | @property (nonatomic) float topMarginLandscape; 38 | @property (nonatomic) UIColor *cellColor; 39 | @property (nonatomic) UIColor *cellSelectedColor; 40 | @property (nonatomic) UIColor *cellTextColor; 41 | @property (nonatomic) UITableViewCellSelectionStyle cellSelectionStyle; 42 | @property (nonatomic) UIFont *cellFont; 43 | @property (nonatomic) float cellHeight; 44 | @property (nonatomic) BOOL fullyOpen; 45 | 46 | - (id)initWithNavigationController:(UINavigationController *)navigationController; 47 | - (id)initWithView:(UIView *)view; 48 | - (void)insertButton:(NSString *)title; 49 | - (void)loadMenu; 50 | - (void)animateDropDown; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /PulldownMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // PulldownMenu.m 3 | // 4 | // Created by Bernard Gatt 5 | // 6 | 7 | #import "PulldownMenu.h" 8 | 9 | @implementation PulldownMenu 10 | 11 | @synthesize menuList, 12 | handle, 13 | cellHeight, 14 | handleHeight, 15 | animationDuration, 16 | topMarginLandscape, 17 | topMarginPortrait, 18 | cellColor, 19 | cellFont, 20 | cellTextColor, 21 | cellSelectedColor, 22 | cellSelectionStyle, 23 | fullyOpen, 24 | delegate; 25 | 26 | - (id)init 27 | { 28 | self = [super init]; 29 | 30 | menuItems = [[NSMutableArray alloc] init]; 31 | 32 | // Setting defaults 33 | cellHeight = 60.0f; 34 | handleHeight = 15.0f; 35 | animationDuration = 0.3f; 36 | topMarginPortrait = 0; 37 | topMarginLandscape = 0; 38 | cellColor = [UIColor grayColor]; 39 | cellSelectedColor = [UIColor blackColor]; 40 | cellFont = [UIFont fontWithName:@"GillSans-Bold" size:19.0f]; 41 | cellTextColor = [UIColor whiteColor]; 42 | cellSelectionStyle = UITableViewCellSelectionStyleDefault; 43 | 44 | return self; 45 | } 46 | 47 | - (id)initWithNavigationController:(UINavigationController *)navigationController 48 | { 49 | self = [self init]; 50 | 51 | if (self) 52 | { 53 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 54 | [[NSNotificationCenter defaultCenter] addObserver: self 55 | selector: @selector(deviceOrientationDidChange:) 56 | name: UIDeviceOrientationDidChangeNotification 57 | object: nil]; 58 | 59 | masterNavigationController = navigationController; 60 | 61 | navigationDragGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragMenu:)]; 62 | navigationDragGestureRecognizer.minimumNumberOfTouches = 1; 63 | navigationDragGestureRecognizer.maximumNumberOfTouches = 1; 64 | 65 | handleDragGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragMenu:)]; 66 | handleDragGestureRecognizer.minimumNumberOfTouches = 1; 67 | handleDragGestureRecognizer.maximumNumberOfTouches = 1; 68 | 69 | [masterNavigationController.navigationBar addGestureRecognizer:navigationDragGestureRecognizer]; 70 | 71 | masterView = masterNavigationController.view; 72 | } 73 | 74 | return self; 75 | } 76 | 77 | - (id)initWithView:(UIView *)view 78 | { 79 | self = [self init]; 80 | 81 | if (self) 82 | { 83 | topMargin = 0; 84 | masterView = view; 85 | } 86 | 87 | return self; 88 | } 89 | 90 | - (void)loadMenu 91 | { 92 | tableHeight = ([menuItems count] * cellHeight); 93 | 94 | [self updateValues]; 95 | 96 | [self setFrame:CGRectMake(0, 0, 0, tableHeight+handleHeight)]; 97 | 98 | fullyOpen = NO; 99 | 100 | menuList = [[UITableView alloc] init]; 101 | [menuList setRowHeight:cellHeight]; 102 | [menuList setDataSource:self]; 103 | [menuList setDelegate:self]; 104 | [self addSubview:menuList]; 105 | 106 | handle = [[UIView alloc] init]; 107 | [handle setBackgroundColor:[UIColor blackColor]]; 108 | 109 | [self addSubview:handle]; 110 | 111 | handleDragGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragMenu:)]; 112 | handleDragGestureRecognizer.minimumNumberOfTouches = 1; 113 | handleDragGestureRecognizer.maximumNumberOfTouches = 1; 114 | [handle addGestureRecognizer:handleDragGestureRecognizer]; 115 | 116 | [self setTranslatesAutoresizingMaskIntoConstraints:NO]; 117 | [handle setTranslatesAutoresizingMaskIntoConstraints:NO]; 118 | [menuList setTranslatesAutoresizingMaskIntoConstraints:NO]; 119 | 120 | [self createConstraints]; 121 | } 122 | 123 | - (void)insertButton:(NSString *)title 124 | { 125 | [menuItems addObject:title]; 126 | } 127 | 128 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 129 | { 130 | [self.delegate menuItemSelected:indexPath]; 131 | } 132 | 133 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 134 | return 1; 135 | } 136 | 137 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 138 | { 139 | return [menuItems count]; 140 | } 141 | 142 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 143 | { 144 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"menuListCell"]; 145 | 146 | if (cell == nil) { 147 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"menuListCell"]; 148 | } 149 | 150 | cell.backgroundColor = cellColor; 151 | 152 | UIView *cellSelectedBackgroundView = [[UIView alloc] initWithFrame:cell.frame]; 153 | cellSelectedBackgroundView.backgroundColor = cellSelectedColor; 154 | cell.selectedBackgroundView = cellSelectedBackgroundView; 155 | cell.selectionStyle = cellSelectionStyle; 156 | 157 | [cell.textLabel setTextColor:cellTextColor]; 158 | cell.textLabel.font = cellFont; 159 | [cell.textLabel setText:[menuItems objectAtIndex:indexPath.item]]; 160 | 161 | return cell; 162 | } 163 | 164 | - (void)dragMenu:(UIPanGestureRecognizer *)sender 165 | { 166 | if ([sender state] == UIGestureRecognizerStateChanged) 167 | { 168 | CGPoint gesturePosition = [sender translationInView:masterNavigationController.navigationBar]; 169 | CGPoint newPosition = gesturePosition; 170 | 171 | newPosition.x = self.frame.size.width / 2; 172 | 173 | if (fullyOpen) 174 | { 175 | if (newPosition.y < 0) 176 | { 177 | newPosition.y += ((self.frame.size.height / 2) + topMargin); 178 | 179 | [self setCenter:newPosition]; 180 | } 181 | } 182 | else 183 | { 184 | newPosition.y += -((self.frame.size.height / 2) - topMargin); 185 | 186 | if (newPosition.y <= ((self.frame.size.height / 2) + topMargin)) 187 | { 188 | [self setCenter:newPosition]; 189 | } 190 | } 191 | } 192 | else if ([sender state] == UIGestureRecognizerStateEnded) 193 | { 194 | [self animateDropDown]; 195 | } 196 | } 197 | 198 | - (void)animateDropDown 199 | { 200 | 201 | [UIView animateWithDuration: animationDuration 202 | delay: 0.0 203 | options: UIViewAnimationOptionCurveEaseOut 204 | animations:^{ 205 | if (fullyOpen) 206 | { 207 | 208 | self.center = CGPointMake(self.frame.size.width / 2, -((self.frame.size.height / 2) + topMargin)); 209 | fullyOpen = NO; 210 | } 211 | else 212 | { 213 | self.center = CGPointMake(self.frame.size.width / 2, ((self.frame.size.height / 2) + topMargin)); 214 | fullyOpen = YES; 215 | } 216 | } 217 | completion:^(BOOL finished){ 218 | [delegate pullDownAnimated:fullyOpen]; 219 | }]; 220 | } 221 | 222 | - (void)createConstraints 223 | { 224 | 225 | NSLayoutConstraint *pullDownTopPositionConstraint = [NSLayoutConstraint 226 | constraintWithItem:self 227 | attribute:NSLayoutAttributeTop 228 | relatedBy:NSLayoutRelationEqual 229 | toItem:masterView 230 | attribute:NSLayoutAttributeTop 231 | multiplier:1.0 232 | constant:-self.frame.size.height]; 233 | 234 | NSLayoutConstraint *pullDownCenterXPositionConstraint = [NSLayoutConstraint 235 | constraintWithItem:self 236 | attribute:NSLayoutAttributeCenterX 237 | relatedBy:NSLayoutRelationEqual 238 | toItem:masterView 239 | attribute:NSLayoutAttributeCenterX 240 | multiplier:1.0 241 | constant:0]; 242 | 243 | NSLayoutConstraint *pullDownWidthConstraint = [NSLayoutConstraint 244 | constraintWithItem:self 245 | attribute:NSLayoutAttributeWidth 246 | relatedBy:NSLayoutRelationEqual 247 | toItem:masterView 248 | attribute:NSLayoutAttributeWidth 249 | multiplier:1.0 250 | constant:0]; 251 | 252 | NSLayoutConstraint *pullDownHeightMaxConstraint = [NSLayoutConstraint 253 | constraintWithItem:self 254 | attribute:NSLayoutAttributeHeight 255 | relatedBy:NSLayoutRelationLessThanOrEqual 256 | toItem:masterView 257 | attribute:NSLayoutAttributeHeight 258 | multiplier:0.5 259 | constant:0]; 260 | 261 | pullDownHeightMaxConstraint.priority = 1000; 262 | 263 | NSLayoutConstraint *pullDownHeightConstraint = [NSLayoutConstraint 264 | constraintWithItem:self 265 | attribute:NSLayoutAttributeHeight 266 | relatedBy:0 267 | toItem:nil 268 | attribute:NSLayoutAttributeNotAnAttribute 269 | multiplier:1.0 270 | constant:tableHeight+handleHeight]; 271 | 272 | pullDownHeightConstraint.priority = 900; 273 | 274 | NSLayoutConstraint *pullHandleWidthConstraint = [NSLayoutConstraint 275 | constraintWithItem:handle 276 | attribute:NSLayoutAttributeWidth 277 | relatedBy:NSLayoutRelationEqual 278 | toItem:masterView 279 | attribute:NSLayoutAttributeWidth 280 | multiplier:1.0 281 | constant:0]; 282 | 283 | NSLayoutConstraint *pullHandleHeightConstraint = [NSLayoutConstraint 284 | constraintWithItem:handle 285 | attribute:NSLayoutAttributeHeight 286 | relatedBy:0 287 | toItem:nil 288 | attribute:NSLayoutAttributeNotAnAttribute 289 | multiplier:1.0 290 | constant:handleHeight]; 291 | 292 | NSLayoutConstraint *pullHandleBottomPositionConstraint = [NSLayoutConstraint 293 | constraintWithItem:handle 294 | attribute:NSLayoutAttributeBottom 295 | relatedBy:NSLayoutRelationEqual 296 | toItem:self 297 | attribute:NSLayoutAttributeBottom 298 | multiplier:1.0 299 | constant:0]; 300 | 301 | NSLayoutConstraint *pullHandleCenterPositionConstraint = [NSLayoutConstraint 302 | constraintWithItem:handle 303 | attribute:NSLayoutAttributeCenterX 304 | relatedBy:0 305 | toItem:self 306 | attribute:NSLayoutAttributeCenterX 307 | multiplier:1.0 308 | constant:0]; 309 | 310 | NSLayoutConstraint *menuListHeightMaxConstraint = [NSLayoutConstraint 311 | constraintWithItem:menuList 312 | attribute:NSLayoutAttributeHeight 313 | relatedBy:NSLayoutRelationLessThanOrEqual 314 | toItem:masterView 315 | attribute:NSLayoutAttributeHeight 316 | multiplier:1.0 317 | constant:-topMargin]; 318 | 319 | NSLayoutConstraint *menuListHeightConstraint = [NSLayoutConstraint 320 | constraintWithItem:menuList 321 | attribute:NSLayoutAttributeHeight 322 | relatedBy:NSLayoutRelationEqual 323 | toItem:self 324 | attribute:NSLayoutAttributeHeight 325 | multiplier:1.0 326 | constant:-handleHeight]; 327 | 328 | NSLayoutConstraint *menuListWidthConstraint = [NSLayoutConstraint 329 | constraintWithItem:menuList 330 | attribute:NSLayoutAttributeWidth 331 | relatedBy:NSLayoutRelationEqual 332 | toItem:self 333 | attribute:NSLayoutAttributeWidth 334 | multiplier:1.0 335 | constant:0]; 336 | 337 | NSLayoutConstraint *menuListCenterXPositionConstraint = [NSLayoutConstraint 338 | constraintWithItem:menuList 339 | attribute:NSLayoutAttributeCenterX 340 | relatedBy:NSLayoutRelationEqual 341 | toItem:self 342 | attribute:NSLayoutAttributeCenterX 343 | multiplier:1.0 344 | constant:0]; 345 | 346 | NSLayoutConstraint *menuListTopPositionConstraint = [NSLayoutConstraint 347 | constraintWithItem:menuList 348 | attribute:NSLayoutAttributeTop 349 | relatedBy:NSLayoutRelationEqual 350 | toItem:self 351 | attribute:NSLayoutAttributeTop 352 | multiplier:1.0 353 | constant:0]; 354 | 355 | [masterView addConstraint: pullDownTopPositionConstraint]; 356 | [masterView addConstraint: pullDownCenterXPositionConstraint]; 357 | [masterView addConstraint: pullDownWidthConstraint]; 358 | [masterView addConstraint: pullDownHeightConstraint]; 359 | [masterView addConstraint: pullDownHeightMaxConstraint]; 360 | 361 | [masterView addConstraint: pullHandleHeightConstraint]; 362 | [masterView addConstraint: pullHandleWidthConstraint]; 363 | [masterView addConstraint: pullHandleBottomPositionConstraint]; 364 | [masterView addConstraint: pullHandleCenterPositionConstraint]; 365 | 366 | [masterView addConstraint: menuListHeightMaxConstraint]; 367 | [masterView addConstraint: menuListHeightConstraint]; 368 | [masterView addConstraint: menuListWidthConstraint]; 369 | [masterView addConstraint: menuListCenterXPositionConstraint]; 370 | [masterView addConstraint: menuListTopPositionConstraint]; 371 | 372 | } 373 | 374 | - (void)deviceOrientationDidChange:(NSNotification *)notification 375 | { 376 | UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; 377 | 378 | if (orientation == UIDeviceOrientationFaceUp || orientation == UIDeviceOrientationFaceDown || orientation == UIDeviceOrientationUnknown || currentOrientation == orientation) { 379 | return; 380 | } 381 | 382 | currentOrientation = orientation; 383 | 384 | [self performSelector:@selector(orientationChanged) withObject:nil afterDelay:0]; 385 | } 386 | 387 | - (void)orientationChanged 388 | { 389 | [self updateValues]; 390 | 391 | UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; 392 | if ((UIDeviceOrientationIsPortrait(currentOrientation) && UIDeviceOrientationIsPortrait(orientation)) || 393 | (UIDeviceOrientationIsLandscape(currentOrientation) && UIDeviceOrientationIsLandscape(orientation))) { 394 | 395 | currentOrientation = orientation; 396 | 397 | if (fullyOpen) 398 | { 399 | [self animateDropDown]; 400 | } 401 | 402 | return; 403 | } 404 | } 405 | 406 | - (void)updateValues 407 | { 408 | topMargin = 0; 409 | 410 | BOOL isStatusBarShowing = ![[UIApplication sharedApplication] isStatusBarHidden]; 411 | 412 | if (UIInterfaceOrientationIsLandscape(self.window.rootViewController.interfaceOrientation)) { 413 | if (isStatusBarShowing) { topMargin = [UIApplication.sharedApplication statusBarFrame].size.width; } 414 | topMargin += topMarginLandscape; 415 | } 416 | else 417 | { 418 | if (isStatusBarShowing) { topMargin = [UIApplication.sharedApplication statusBarFrame].size.height; } 419 | topMargin += topMarginPortrait; 420 | } 421 | 422 | if (masterNavigationController != nil) 423 | { 424 | topMargin += masterNavigationController.navigationBar.frame.size.height; 425 | } 426 | } 427 | 428 | @end 429 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iOSPullDownMenu 2 | =============== 3 | 4 | A pulldown menu for all iOS devices. 5 | 6 | The pulldown menu supports both navigation controllers and views, users can either pull it down or activate by tapping a button. 7 | 8 |

9 | iOSPullDownMenu 10 | iOSPullDownMenu Button 11 |

12 | 13 | ### To connect the pulldown menu to the navigation controller 14 | 15 | .h 16 | 17 | ```objective-c 18 | #import "PulldownMenu.h" 19 | 20 | @interface MasterNavigationController : UINavigationController { 21 | PulldownMenu *pulldownMenu; 22 | } 23 | 24 | @property (nonatomic, retain) PulldownMenu *pulldownMenu; 25 | 26 | @end 27 | ``` 28 | 29 | .m 30 | 31 | ```objective-c 32 | - (void)viewDidLoad 33 | { 34 | [super viewDidLoad]; 35 | 36 | pulldownMenu = [[PulldownMenu alloc] initWithNavigationController:self]; 37 | [self.view insertSubview:pulldownMenu belowSubview:self.navigationBar]; 38 | 39 | [pulldownMenu insertButton:@"Menu Item 1"]; 40 | [pulldownMenu insertButton:@"Menu Item 2"]; 41 | [pulldownMenu insertButton:@"Menu Item 3"]; 42 | 43 | pulldownMenu.delegate = self; 44 | 45 | [pulldownMenu loadMenu]; 46 | } 47 | ``` 48 | 49 | ### Or connect the pulldown menu to a view instead of the navigation controller. 50 | 51 | .h 52 | 53 | ```objective-c 54 | #import "PulldownMenu.h" 55 | 56 | @interface MainViewController : UIViewController { 57 | PulldownMenu *pulldownMenu; 58 | } 59 | ``` 60 | 61 | .m 62 | 63 | ```objective-c 64 | - (void)viewDidLoad 65 | { 66 | [super viewDidLoad]; 67 | 68 | pulldownMenu = [[PulldownMenu alloc] initWithView:self.view]; 69 | [self.view addSubview:pulldownMenu]; 70 | 71 | [pulldownMenu insertButton:@"Menu Item 1"]; 72 | [pulldownMenu insertButton:@"Menu Item 2"]; 73 | [pulldownMenu insertButton:@"Menu Item 3"]; 74 | 75 | pulldownMenu.delegate = self; 76 | 77 | [pulldownMenu loadMenu]; 78 | } 79 | 80 | - (IBAction)menuTap:(id)sender { 81 | [pulldownMenu animateDropDown]; 82 | } 83 | ``` 84 | 85 | ### Events fired by the Pulldown Menu 86 | The component fires 2 events, #1 when a menu item is selected and #2 when the pull down is fully animated. 87 | 88 | ```objective-c 89 | -(void)menuItemSelected:(NSIndexPath *)indexPath 90 | { 91 | NSLog(@"%d",indexPath.item); 92 | } 93 | 94 | -(void)pullDownAnimated:(BOOL)open 95 | { 96 | if (open) 97 | { 98 | NSLog(@"Pull down menu open!"); 99 | } 100 | else 101 | { 102 | NSLog(@"Pull down menu closed!"); 103 | } 104 | } 105 | ``` 106 | 107 | ### Open / Close on demand 108 | The pull down/up animation can be called on demand 109 | 110 | From a view inside a navigation controller: 111 | 112 | ```objective-c 113 | [((MasterNavigationController *)self.navigationController).pulldownMenu animateDropDown]; 114 | ``` 115 | 116 | From a view 117 | 118 | ```objective-c 119 | [pulldownMenu animateDropDown]; 120 | ``` 121 | 122 | ### Styles 123 | Apart from having both table view and handle exposed, a number of styling properties are available out of the box. 124 | 125 | ```objective-c 126 | cellHeight = 60; 127 | handleHeight = 15; 128 | animationDuration = 0.3f; 129 | topMarginPortrait = 0; 130 | topMarginLandscape = 0; 131 | cellColor = [UIColor grayColor]; 132 | cellSelectedColor = [UIColor blackColor]; 133 | cellFont = [UIFont fontWithName:@"GillSans-Bold" size:19.0f]; 134 | cellTextColor = [UIColor whiteColor]; 135 | cellSelectionStyle = UITableViewCellSelectionStyleDefault; 136 | ``` 137 | 138 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/BernardGatt/iospulldownmenu/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 139 | 140 | --------------------------------------------------------------------------------