Jaki jest najlepszy sposób na stworzenie menu sidewipe, takiego jak w nowej aplikacji Facebooka na iOS?

155

Wygląda na to, że menu przesuwne w bok stają się coraz powszechniejszym elementem interfejsu, ponieważ w każdej aplikacji na iPhone'a gromadzi się więcej informacji. Facebook włączył go do swojej najnowszej wersji, a nowa aplikacja Gmail wydaje się również zawierać go . Zastanawiałem się, czy ktoś miał przemyślenia na temat najbardziej wydajnego sposobu tworzenia czegoś takiego, ponieważ staje się on coraz powszechniejszym elementem interfejsu. Chociaż mam własne przemyślenia na temat tego, jak to zbudować, jestem ciekawy, co myślą inni ludzie.

Nawigacja przesuwania na Facebooku

Nick ONeill
źródło
Zauważyłem, że gdy aktywowane jest menu sidewipe, nie można nic zrobić z wysuniętą częścią widoku. Mój pomysł polegał na wyrenderowaniu bieżącego widoku na obrazie i wyświetleniu jego części, gdy aktywowany jest sidewipe
Denis
5
Czyj profil na Facebooku zajrzałeś, Nick O'Neill? :-p
Constantino Tsarouhas
1
możliwy duplikat SplitView, ale na iPhonie
JosephH
1
@Zoidberg Chcę dowiedzieć się więcej na ten temat. Osobiście lubię sidenav! Ale jestem programistą, a nie projektantem. Co sprawia, że ​​ten UX jest słaby?
Robert Karl
3
Niech każdy obejrzy Projektowanie intuicyjnych doświadczeń użytkownika, aby dowiedzieć się, jakie problemy zidentyfikowało Apple z tak zwanymi „menu hamburgerowymi”
vikingosegundo

Odpowiedzi:

34

Niedawno natknąłem się na to, tak naprawdę nie patrzyłem na kod ani nie testowałem kontrolki, ale wygląda na to, że może to być bardzo przyzwoity punkt wyjścia.

jtrevealsidebar

Edycja: Czytelnik powinien również spojrzeć na inne odpowiedzi :)

Zaky niemiecki
źródło
Nie pozwala użytkownikowi na ślizganie się
cannyboy
6
Jeśli szukasz funkcji przesuwania. Wstaw aparat rozpoznawania gestów do widoku, a następnie zamień przycisk przełączania na moduł rozpoznawania gestów.
CJ Teuben
Ale co, jeśli chcesz przesuwać i mieć możliwość przełączania?
jsetting32
To jest przerwane, więc chciałbym przenieść się gdzie indziej.
Mike Flynn
84

Jest do tego świetna biblioteka autorstwa Toma Adriaenssena: Inferis / ViewDeck

Jest bardzo łatwy w użyciu i ma dość dużą liczbę zwolenników.

EDYTOWAĆ:

Jeśli szukasz czegoś nieco lżejszego , sprawdź: wzajemny mobilny / MMDrawerController

Nie ma wszystkich funkcji ViewDeck, ale jest prostszy w modyfikowaniu i rozszerzaniu.

Chris Knadler
źródło
6
Polecam wszystkim to.
Almas Adilbek
3
najlepsze rozwiązanie: elastyczne i proste w użyciu
HotJard
2
Wypróbowałem to i znalazłem wiele błędów w ich przykładzie. Po kilku szybkich kliknięciach kontrolery widoku są źle ustawione
Torsten B
13
Używaliśmy go w kilku aplikacjach, ale utrzymanie i dodawanie do niego nowych funkcji było bardzo trudne. Cierpiał również z powodu zbyt wielu prób wdrożenia. Postanowiliśmy napisać własny o nazwie MMDrawerController. Lekki i skoncentrowany: github.com/mutualmobile/MMDrawerController
kcharwood
1
MMDrawerController to bardzo dobra biblioteka obsługująca uniwersalną aplikację. aplikacje na iPhone'a i iPada
Erhan Demirci,
48

Stworzyłem do tego bibliotekę. Nazywa się MFSideMenu .

Obejmuje między innymi obsługę iPhone'a i iPada, portretu + krajobrazu, menu po lewej lub prawej stronie, UITabBarController i gestów przesuwania.

Michael Frederick
źródło
To świetny projekt… ale nie obsługuje trybu poziomego… Czy możesz nam podpowiedzieć, jak używać go również w trybie poziomym… Dzięki
Sameera Chathuranga
Dzięki @SameeraChathuranga. Mam nadzieję, że jeśli będę miał trochę czasu, dodam obsługę krajobrazu. Jedyną częścią, która nie obsługuje krajobrazu, jest SideMenuViewController ... Myślę, że musiałbyś zrobić coś takiego jak druga odpowiedź tutaj: stackoverflow.com/questions/2508630/… . Zapraszam do forka i przyczynienia się do repozytorium!
Michael Frederick
I polecam to jest odpowiedź na to pytanie .. nie powyższe .. jest zbyt skomplikowane .. to bardzo, bardzo łatwe w obsłudze :) jeśli ktoś chce, mogę wysłać kod, jak pracować z oriantacją ..: )
Sameera Chathuranga
6
Dla wszystkich zainteresowanych właśnie dodałem obsługę krajobrazu do biblioteki.
Michael Frederick
1
@AlmasAdilbek, czy możesz wyjaśnić, które części Twojej aplikacji doświadczyły spadku wydajności? Z przyjemnością dokonam wszelkich niezbędnych optymalizacji.
Michael Frederick
29

Sprawdź MMDrawerController:

MMDrawerController

Nie mogliśmy znaleźć biblioteki, która nam się podobała, więc po prostu stworzyliśmy własną.

kcharwood
źródło
10
Chciałem tylko powiedzieć, że jest to ZDECYDOWANIE najlepsza implementacja wymieniona tutaj. Osoby nowe w tym wątku: użyj MMDrawerController.
mxcl
Doceń miłe słowa!
kcharwood
Używałem ViewDeck od zeszłego roku, ale ostatnio przerzuciłem się na MMDrawerController i polecam wszystkim korzystanie z tego. Jedna z najlepszych implementacji w ogólnych bibliotekach. Dzięki Kevin :)
Tariq
Sprawdziłem ten MMDrawerController i chcę powiedzieć, że to naprawdę najlepsza opcja dla menu bocznego. Doceniony! Wielkie podziękowania dla @Michael Frederick
Resty
Czy to obsługuje iOS8? Dzięki
Brad Thomas
12

Klucz pomysł , że trzeba zestaw ramek self.navigationController.view lub centrum . Są dwa zdarzenia , z którymi musisz sobie poradzić. (1) bar Przycisk Naciśnięcie pozycji . (2) gest przesuwania z powodu przeciągnięcia.

Możesz wysłać kontroler widoku w tło w następujący sposób:

[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);
            }];
        }
    }
}
János
źródło
Działa idealnie i bez zbytniego kodu. Musisz tylko określić granice, w których użytkownik może przesuwać, aby Twój widok
Moisés Olmedo
@Janos sir uprzejmie pomóż mi, brakuje mi tego przez więcej dni
Ragul
Boczne menu stworzyłem za pomocą twojego powyższego kodu, gdy wysuwana tylna strona pokazuje tylko ciemny kolor, a widok uprzejmie mi pomóż
Ragul
Sir, działa dobrze, używam tylko powyższego kodu. jedyną rzeczą jest to, że widok tła nie pokazuje, że go poprawiłem, pomóż mi pokazać widok tła
Ragul
@ János i gdzie użyć tej linii kodu [self.view sendSubviewToBack: menuViewController.view];
Ragul
11

Jeszcze lepszy jest JASidePanels . Łatwa implementacja i działa zarówno na iPhonie, jak i iPadzie.

Link do Github: JASidePanels

user2330922
źródło
11

Wdrożenie Facebooka umieszcza UIPanGestureRecognizer na UINavigationBar. W ten sposób pozwalając złapać przeciągnięcia tam, gdzie jest to potrzebne.

Pozwala to na takie rzeczy, jak bezpośrednie rozpoznawanie kierunku dotyku w x / z, a także prędkości, z jaką się pojawiały.

Również taki rodzaj majstrowania przy UIViews (więcej niż jeden na ekranie na raz z ewidentnie różnymi zadaniami -> a więc różnymi kontrolerami) powinien (kuszę się powiedzieć, że musi) korzystać z nowych funkcji iOS ViewController-Containment. Każda implementacja bez tego jest po prostu zła, ponieważ majstruje przy hierarchii widoków w sposób niezamierzony przez Apple.

Na marginesie: jeśli naprawdę ciekawi cię, jak można to zrobić tak blisko Facebooka, jak to tylko możliwe, sprawdź projekt, który otwieram, pochodzący z Github PKRevealController .

pkluz
źródło
Nie ma nic złego (lub niezgodnego z HIG) w zarządzaniu niestandardowym interfejsem użytkownika opartym na UIView. Zawieranie kontrolera widoku pojawiło się zbyt późno, więc zastosowano wiele innych podejść. Powiązany artykuł: blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers
Jacob Jennings
1
Duże zmiany przez długi czas nie będą ukierunkowane na minimalną wersję iOS 5, ponieważ duża liczba użytkowników nie będzie aktualizować się przez bardzo długi czas. Chociaż wiele z nich to „hacki”, jak mówisz, z pewnością można to zrobić poprawnie. Pozostawanie w ramach jest zdecydowanie bezpiecznym rozwiązaniem, ale czasami unikalny i niestandardowy interfejs użytkownika wymaga więcej.
Jacob Jennings
8

Istnieją dziesiątki różnych bibliotek, które programiści stworzyli, aby zaimplementować nawigację w stylu Facebook / Path dla aplikacji na iOS. Mogę wymienić je wszystkie, ale ponieważ poprosiłeś o najlepszy, oto moje dwie opcje, których używam do implementacji menu nawigacji z bocznym przesuwaniem:

1) ECSlidingViewController Jest bardzo łatwy w implementacji i używaniu również scenorysów. Nie miałem żadnych problemów z jego implementacją, nie otrzymałem żadnych błędów ani niczego w tym stylu. Link, który podałem, zawiera prawie wszystkie wyjaśnienia, jak z niego korzystać.

2) SWRevealViewController Ta biblioteka jest łatwa w użyciu i TUTAJ można znaleźć samouczek , który pokazuje, jak ją wdrożyć wraz z wszystkimi wyjaśnieniami wraz z obrazami. Możesz po prostu postępować zgodnie z samouczkiem i podziękować mi później.

AJ112
źródło
1
SWRevealViewController jest najprostszy, używany bez błędów działa dobrze
vishal dharankar
7

Inną opcją jest użycie Scringo . Daje ci boczne menu, takie jak w aplikacjach youtube / facebook, a także wszelkiego rodzaju wbudowane funkcje w menu, które możesz dodać (np. Czat, zapraszanie znajomych itp.)

Dodanie menu bocznego polega po prostu na wywołaniu [ScringoAgent startSession ...], a całą konfigurację można przeprowadzić na stronie.

Węgry54
źródło
4

To pytanie jest dość popularne, ale wszystko sprowadzało się do łatwego importowania i używania dla mnie ... Oto czego używam ... SWRevealController .

Dziwię się, że ludzie za tym tęsknią ... To naprawdę WSPANIAŁE !!! i łatwy w użyciu. Deweloper zawiera nawet kilka przykładowych projektów, które pozwalają zobaczyć, jak używać go w różnych scenariuszach.

jsetting32
źródło
2

Zarówno Gmail, jak i Facebook intensywnie wykorzystują widoki internetowe, więc trudno powiedzieć, co to jest kod natywny, a co renderowany HTML. Jednak patrząc na interfejs, wygląda na to, że umieścili UITableView o szerokości węższej niż szerokość ekranu (320 punktów) pod UIView, który zawiera zawartość, którą chcą wyświetlić. Wybranie różnych wierszy widoku tabeli prawdopodobnie powoduje zamianę widoku podrzędnego widoku zawartości.

Przynajmniej tak bym podszedł do problemu. Trudno dyktować, co to powinno być. Po prostu wskocz i zacznij eksperymentować!

Jesion Bruzda
źródło
Hej, czy ktoś może mi powiedzieć, jak utworzyć widok przesuwania typu facebooka i panel menu w
Androidzie
1

Jeśli chcesz, zrobiłem to repozytorium na githubie GSSlideMenu To pozwala na stworzenie menu w stylu Facebooka, ALE z "wstecz" webView (generalnie wszystkie repozytoria, które znalazłem, mają tableView jako widok "z tyłu").

JAA
źródło
1

widok po prawej stronie mógłby znajdować się wewnątrz podklasy UIScrollView. W tej podklasie można zastąpić, - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventaby zwrócić TAK tylko wtedy, gdy użytkownik dotknął widoku podrzędnego. W ten sposób możesz umieścić swój przezroczysty widok UIScrollView nad dowolnym innym widokiem i przejść przez każde zdarzenie dotykowe, które ma miejsce poza widokiem podrzędnym; a materiały do ​​przewijania otrzymujesz za darmo.

Na przykład:

- (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);
}

lub:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    return [subview pointInside:
             [self convertPoint:point toView:subview] withEvent:event];
javieralog
źródło
1

Spróbuj dodać swoje menu (do którego przesuń palcem, aby przejść) pod głównym widokiem. Rozpocznij subskrybowanie wydarzeń dotykowych w widoku.

Zaimplementuj touchesMoved:i sprawdź, czy pierwszy gesturejest pionowy (przewiń widok główny, jeśli to konieczne), czy poziomy (tutaj chcesz pokazać menu). Jeśli jest pozioma, zacznij wywoływać inną metodę, - (void)scrollAwayMainView:(NSUInteger)pixelsza każdym razem, gdy touchesMoved:zostanie wywołana, oblicz liczbę pikseli oddalonych od punktu początkowego, w którym powinien znajdować się główny widok, i przekaż tę liczbę do metody. W tej implementacji metod uruchom następujący kod:

[mainView scrollRectToVisible:CGRectMake:(pixels, 
                                          mainView.origin.y, 
                                          mainView.size.width, 
                                          mainView.size.height];
aopsfan
źródło
0

Sprawdź moje rozwiązanie na to pytanie:

Jak stworzyć niestandardowe menu slajdów bez biblioteki innej firmy?

Jedna klasa, którą musisz tylko podklasować i wypełnić treścią.

Oto klasa. Aby uzyskać więcej informacji, zobacz powyższy 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
David Tarnok
źródło