What's the best way to develop a sideswipe menu like the one in Facebook's new iOS app?

Objective CIosCocoa Touch

Objective C Problem Overview


It appears that side-swipe menus are becoming a more common interface element as more information gets crammed into each iPhone app. Facebook has included it in their latest version and the new Gmail app appears to include it as well. I was wondering if anybody had thoughts on the most efficient way of developing something like this as it's becoming a more common interface element. While I have my own thoughts on how to build this, I'm curious to hear what other people think.

Facebook swipe navigation

Objective C Solutions


Solution 1 - Objective C

There is a great library for this by Tom Adriaenssen: Inferis/ViewDeck

It's very easy to use and has a fairly large following.

EDIT:

For something a little more lightweight, check out: mutualmobile/MMDrawerController

It doesn't have all of the features of ViewDeck but is simpler to modify and extend.

Solution 2 - Objective C

I created a library for this. It is called MFSideMenu.

Among other things it includes support for iphone+ipad, portrait+landscape, menu on the left or right side, UITabBarController, and pan gestures.

Solution 3 - Objective C

I recently came across this, didn't actually look at the code or test the control, but looks like it may be a very decent starting point.

jtrevealsidebar

Edit: The reader should also take a look at the other answers :)

Solution 4 - Objective C

Check out MMDrawerController:

MMDrawerController

We couldn't find a library that we liked, so we just rolled our own.

Solution 5 - Objective C

The key idea that you have to set self.navigationController.view's frame or center. There is two event that you have to handle. (1) barButtonItem press. (2) pan gesture because of swiping.

You can send view controller to the background like this:

[self.view sendSubviewToBack:menuViewController.view];

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(buttonPressed:)];
    UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    [self.navigationController.view addGestureRecognizer:panGestureRecognizer];
    self.navigationItem.leftBarButtonItem = barButtonItem;
}

- (void)buttonPressed:(id)sender {
    
    CGRect destination = self.navigationController.view.frame;
    
    if (destination.origin.x > 0) {
        destination.origin.x = 0;
    } else {
        destination.origin.x = 320;
    }
    
    [UIView animateWithDuration:0.25 animations:^{
        self.navigationController.view.frame = destination;
    }];
}

- (void)handlePan:(UIPanGestureRecognizer *)recognizer
{
    static CGPoint originalCenter;
    
    if (recognizer.state == UIGestureRecognizerStateBegan)
    {
        originalCenter = recognizer.view.center;
        
    } else if (recognizer.state == UIGestureRecognizerStateChanged)
    {
        CGPoint translation = [recognizer translationInView:self.view];
        
        recognizer.view.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y);
    }
    else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled || recognizer.state == UIGestureRecognizerStateFailed)
    {
        if (recognizer.view.frame.origin.x < 160) {
            [UIView animateWithDuration:0.25 animations:^{
                recognizer.view.center = CGPointMake(384, 487.5);
            }];
        } else {
            [UIView animateWithDuration:0.25 animations:^{
                recognizer.view.center = CGPointMake(384 + 320, 487.5);
            }];
        }
    }
}

Solution 6 - Objective C

Facebook's implementation places a UIPanGestureRecognizer on the UINavigationBar. Thus allowing to catch swipes where it's needed.

That allows to do things like, recognizing the touch direction in x/z directly as well as the speed they occurred with.

Also such kind of tinkering with UIViews (more than one on screen at a time with evidently different tasks -> thus different controllers) should (I'm tempted to say must) use iOS new ViewController-Containment features. Every implementation without that is simply bad as it tinkers with the view hierarchy in a way not intended by Apple.

On a sidenote: If you're really curious on how it can be done as close to Facebook's way as possible, check out the project I open sourced on Github PKRevealController .

Solution 7 - Objective C

Even better is JASidePanels. Easily implemented and works for both iPhone and iPad.

Github link: JASidePanels

Solution 8 - Objective C

There are tens of different libraries that developers have created to implement the Facebook/Path style navigation for iOS apps. I can list all of them but as you have asked for the best one, here are my two choices that i use to implement the side swipe navigation menu:

  1. ECSlidingViewController Its very easy to implement and use Storyboards also. I haven't had any issues implementing it nor received any errors or anything like that. The link i provided has pretty much all the explanation in order to use it.

  2. SWRevealViewController This library is easy to use and you can find a tutorial HERE, that shows how to implement it with all the explanation along with images. You can just follow the tutorial and thank me later.

Solution 9 - Objective C

Another option is to use Scringo . It gives you a side-menu like in youtube/facebook apps, and also all kind of built-in features in the menu that you can choose to add (e.g chat, invite friends, etc...)

Adding the side-menu is simply by calling [ScringoAgent startSession...] and all configuration can be done on the site.

Solution 10 - Objective C

Refer to here, a very good starting point: slide out navigation like facebook and path

Solution 11 - Objective C

This question is quite popular but it all came down to easy importing and use for me... Here is what I use... SWRevealController.

I'm surprised people miss it... It really is GREAT!!! and easy to use. The developer even includes a few example projects that allow you to see how to use it in different scenarios.

Solution 12 - Objective C

Solution 13 - Objective C

Both Gmail and Facebook make heavy use of web views, so it's hard to say what's native code and what's rendered HTML. However, looking at the interface, it looks like they've placed a UITableView with a width narrower than the screen width (320pt) underneath of a UIView that contains the content they want to display. Selecting different tableview rows probably swaps out a subview of the content view.

At least, that's how I'd approach the problem. It's hard to dictate what it should be. Just jump right in and start experimenting!

Solution 14 - Objective C

the view in the right could be inside a subclass of UIScrollView. In this subclass you can override - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event to return YES only if the user touched the subview. This way you can place your transparent UIScrollView over any other view and pass through any touch event that takes place outside the subview; and you get scroll stuff for free.

For example:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    CGPoint location=[self convertPoint:point toView:subview];
    return (location.x > 0 && 
            location.x < subview.frame.size.width && 
            location.y > 0 && 
            location.y < subview.frame.size.height);
}

or:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    return [subview pointInside:
             [self convertPoint:point toView:subview] withEvent:event];

Solution 15 - Objective C

Try adding your menu (that you swipe to get to) underneath the main view. Begin subscribing to touch events in the view.

Implement touchesMoved:, and check to see if the first gesture is vertical (scroll the main view, if needed), or horizontal (here you'd want to show the menu). If it's horizontal, begin invoking another method, - (void)scrollAwayMainView:(NSUInteger)pixels, whenever touchesMoved: gets called, calculating the number of pixels away from the starting point the main view should be, and passing that number into the method. In that methods implementation, run the following code:

[mainView scrollRectToVisible:CGRectMake:(pixels, 
                                          mainView.origin.y, 
                                          mainView.size.width, 
                                          mainView.size.height];

Solution 16 - Objective C

If you want I did this repo on github GSSlideMenu It allow you to create a Facebook-style menu BUT with a "back" webView (generally all repos that I've found has a tableView as the "back" view).

Solution 17 - Objective C

this is exactly the clone of the facebook sidebar : GHSidebarNav library

very easy to use. instruction are in

Solution 18 - Objective C

Check out my solution for this question:

https://stackoverflow.com/questions/37045208/how-to-create-custom-slide-menu-without-third-party-library/37047366#37047366

One single class that you only need to subclass and fill in with content.

Here is the class. For more details, see the above link.

#import <UIKit/UIKit.h>

@interface IS_SlideMenu_View : UIView <UIGestureRecognizerDelegate>
{
    UIView* transparentBgView;
    BOOL hidden;
    int lastOrientation;
}

@property (strong, nonatomic) UIView *menuContainerV;

+ (id)sharedInstance;

- (BOOL)isShown;
- (void)hideSlideMenu;
- (void)showSlideMenu;

@end


#import "IS_SlideMenu_View.h"

@implementation IS_SlideMenu_View

+ (id)sharedInstance
{
    static id _sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[[self class] alloc] init];
    });
    
    return _sharedInstance;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    frame = [[[UIApplication sharedApplication] delegate] window].frame;
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        
        transparentBgView = [[UIView alloc] initWithFrame:frame];
        [transparentBgView setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.6]];
        [transparentBgView setAlpha:0];
        transparentBgView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gestureRecognized:)];
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gestureRecognized:)];
        [transparentBgView addGestureRecognizer:tap];
        [transparentBgView addGestureRecognizer:pan];
        
        [self addSubview:transparentBgView];
        
        frame.size.width = 280;
        self.menuContainerV = [[UIView alloc] initWithFrame:frame];
        CALayer *l = self.menuContainerV.layer;
        l.shadowColor = [UIColor blackColor].CGColor;
        l.shadowOffset = CGSizeMake(10, 0);
        l.shadowOpacity = 1;
        l.masksToBounds = NO;
        l.shadowRadius = 10;
        self.menuContainerV.autoresizingMask = UIViewAutoresizingFlexibleHeight;
        
        [self addSubview: self.menuContainerV];
        hidden = YES;
    }
    
    //----- SETUP DEVICE ORIENTATION CHANGE NOTIFICATION -----
    UIDevice *device = [UIDevice currentDevice];
    [device beginGeneratingDeviceOrientationNotifications];
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:device];
    
    lastOrientation = [[UIDevice currentDevice] orientation];
    
    return self;
}

//********** ORIENTATION CHANGED **********
- (void)orientationChanged:(NSNotification *)note
{
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];    
    if(orientation == UIDeviceOrientationPortrait || orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight){
        NSLog(@"%ld",orientation);
        if(!hidden && lastOrientation != orientation){
            [self hideSlideMenu];
            hidden = YES;
            lastOrientation = orientation;
        }
    }
}

- (void)showSlideMenu {
    UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
    self.frame = CGRectMake(0, 0, window.frame.size.width, window.frame.size.height);
    
    [self.menuContainerV setTransform:CGAffineTransformMakeTranslation(-window.frame.size.width, 0)];
    
    [window addSubview:self];
//    [[UIApplication sharedApplication] setStatusBarHidden:YES];
    
    [UIView animateWithDuration:0.5 animations:^{
        [self.menuContainerV setTransform:CGAffineTransformIdentity];
        [transparentBgView setAlpha:1];
    } completion:^(BOOL finished) {
        NSLog(@"Show complete!");
        hidden = NO;
    }];
}

- (void)gestureRecognized:(UIGestureRecognizer *)recognizer
{
    if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
        [self hideSlideMenu];
    } else if ([recognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        static CGFloat startX;
        if (recognizer.state == UIGestureRecognizerStateBegan) {
            startX = [recognizer locationInView:self.window].x;
        } else
        if (recognizer.state == UIGestureRecognizerStateChanged) {
            CGFloat touchLocX = [recognizer locationInView:self.window].x;
            if (touchLocX < startX) {
                [self.menuContainerV setTransform:CGAffineTransformMakeTranslation(touchLocX - startX, 0)];
            }
        } else
        if (recognizer.state == UIGestureRecognizerStateEnded) {
            [self hideSlideMenu];
        }
    }
}

- (void)hideSlideMenu
{
    UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
    window.backgroundColor = [UIColor clearColor];
    [UIView animateWithDuration:0.5 animations:^{
        [self.menuContainerV setTransform:CGAffineTransformMakeTranslation(-self.window.frame.size.width, 0)];
        [transparentBgView setAlpha:0];
    } completion:^(BOOL finished) {
        [self removeFromSuperview];
        [self.menuContainerV setTransform:CGAffineTransformIdentity];
        
//        [[UIApplication sharedApplication] setStatusBarHidden:NO];
        hidden = YES;
        NSLog(@"Hide complete!");
    }];
}

- (BOOL)isShown
{
    return !hidden;
}

@end

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNick ONeillView Question on Stackoverflow
Solution 1 - Objective CChris KnadlerView Answer on Stackoverflow
Solution 2 - Objective CMichael FrederickView Answer on Stackoverflow
Solution 3 - Objective CZaky GermanView Answer on Stackoverflow
Solution 4 - Objective CkcharwoodView Answer on Stackoverflow
Solution 5 - Objective CJánosView Answer on Stackoverflow
Solution 6 - Objective CpkluzView Answer on Stackoverflow
Solution 7 - Objective Cuser2330922View Answer on Stackoverflow
Solution 8 - Objective CAJ112View Answer on Stackoverflow
Solution 9 - Objective Chungary54View Answer on Stackoverflow
Solution 10 - Objective CZTANView Answer on Stackoverflow
Solution 11 - Objective Cjsetting32View Answer on Stackoverflow
Solution 12 - Objective CVictor SiglerView Answer on Stackoverflow
Solution 13 - Objective CAsh FurrowView Answer on Stackoverflow
Solution 14 - Objective CjavieralogView Answer on Stackoverflow
Solution 15 - Objective CaopsfanView Answer on Stackoverflow
Solution 16 - Objective CJAAView Answer on Stackoverflow
Solution 17 - Objective CMax_Power89View Answer on Stackoverflow
Solution 18 - Objective CdtarnokView Answer on Stackoverflow