Naśladuj Facebooka ukryj / pokaż rozwijający / zmniejszający się pasek nawigacji

128

W nowej aplikacji iOS7 Facebook na iPhone'a, gdy użytkownik przewija w górę, navigationBarstopniowo chowa się do punktu, w którym całkowicie znika. Następnie, gdy użytkownik przewija w dół, navigationBarstopniowo się pokazuje.

Jak samodzielnie wdrożyłbyś to zachowanie? Zdaję sobie sprawę z następującego rozwiązania, ale znika ono od razu i nie jest w ogóle związane z szybkością gestu przewijania użytkownika.

[navigationController setNavigationBarHidden: YES animated:YES];

Mam nadzieję, że to nie jest duplikat, ponieważ nie jestem pewien, jak najlepiej opisać zachowanie „rozszerzania / kurczenia się”.

El Mocoso
źródło
2
Te same problemy: stackoverflow.com/questions/21929220/... Uwaga, że jest to niezwykle trudne do absolutnie dopasować zachowanie Safari. Jest tam kilka bardzo, bardzo skomplikowanych zasad!
Fattie
1
W moim projekcie wykorzystałem ten projekt i działał dobrze. Zapoznaj się z jego dokumentacją.
Vinicius
github.com/bryankeller/BLKF flexibleHeightBar pozwoli Ci robić to, co chcesz i nie tylko. Pozwala dokładnie określić wygląd paska na każdym etapie przejścia od zmaksymalizowanego do zminimalizowanego. Pozwala nawet określić własne zachowania, dzięki czemu może działać jak Safari, Facebook lub inna aplikacja.
blkhp19
Nie użyłem uinavigationbar, ale zamiast tego dodałem uiview. Widok replikujący pasek nawigacji będzie się rozszerzał i kurczył na podstawie przewijania. Do wykonania zadania wykorzystałem metodę delegata scrollViewDidScroll. Możesz sprawdzić i wykonać poniższy kod źródłowy .. dropbox.com/s/b2c0zw6yvchaia5/FailedBanks.zip?dl=0
Deepak Thakur

Odpowiedzi:

162

Rozwiązanie podane przez @peerless to świetny początek, ale animacja uruchamia się tylko wtedy, gdy zaczyna się przeciąganie, bez uwzględnienia szybkości przewijania. Powoduje to bardziej niepewne wrażenia niż w aplikacji Facebook. Aby dopasować się do zachowania Facebooka, musimy:

  • ukryj / pokaż pasek nawigacyjny z prędkością proporcjonalną do szybkości przeciągania
  • uruchom animację, aby całkowicie ukryć pasek, jeśli przewijanie zatrzyma się, gdy pasek jest częściowo ukryty
  • znikają elementy paska nawigacyjnego, gdy pasek się kurczy.

Najpierw będziesz potrzebować następującej właściwości:

@property (nonatomic) CGFloat previousScrollViewYOffset;

A oto UIScrollViewDelegatemetody:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGRect frame = self.navigationController.navigationBar.frame;
    CGFloat size = frame.size.height - 21;
    CGFloat framePercentageHidden = ((20 - frame.origin.y) / (frame.size.height - 1));
    CGFloat scrollOffset = scrollView.contentOffset.y;
    CGFloat scrollDiff = scrollOffset - self.previousScrollViewYOffset;
    CGFloat scrollHeight = scrollView.frame.size.height;
    CGFloat scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom;

    if (scrollOffset <= -scrollView.contentInset.top) {
        frame.origin.y = 20;
    } else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) {
        frame.origin.y = -size;
    } else {
        frame.origin.y = MIN(20, MAX(-size, frame.origin.y - scrollDiff));
    }

    [self.navigationController.navigationBar setFrame:frame];
    [self updateBarButtonItems:(1 - framePercentageHidden)];
    self.previousScrollViewYOffset = scrollOffset;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self stoppedScrolling];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView 
                  willDecelerate:(BOOL)decelerate
{
    if (!decelerate) {
        [self stoppedScrolling];
    }
}

Będziesz także potrzebować tych metod pomocniczych:

- (void)stoppedScrolling
{
    CGRect frame = self.navigationController.navigationBar.frame;
    if (frame.origin.y < 20) {
        [self animateNavBarTo:-(frame.size.height - 21)];
    }
}

- (void)updateBarButtonItems:(CGFloat)alpha
{
    [self.navigationItem.leftBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem* item, NSUInteger i, BOOL *stop) {
        item.customView.alpha = alpha;
    }];
    [self.navigationItem.rightBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem* item, NSUInteger i, BOOL *stop) {
        item.customView.alpha = alpha;
    }];
    self.navigationItem.titleView.alpha = alpha;
    self.navigationController.navigationBar.tintColor = [self.navigationController.navigationBar.tintColor colorWithAlphaComponent:alpha];
}

- (void)animateNavBarTo:(CGFloat)y
{
    [UIView animateWithDuration:0.2 animations:^{
        CGRect frame = self.navigationController.navigationBar.frame;
        CGFloat alpha = (frame.origin.y >= y ? 0 : 1);
        frame.origin.y = y;
        [self.navigationController.navigationBar setFrame:frame];
        [self updateBarButtonItems:alpha];
    }];
}

Aby uzyskać nieco inne zachowanie, zamień wiersz, który zmienia położenie paska podczas przewijania ( elseblok wscrollViewDidScroll ), tym:

frame.origin.y = MIN(20, 
                     MAX(-size, frame.origin.y - 
                               (frame.size.height * (scrollDiff / scrollHeight))));

Spowoduje to ustawienie paska na podstawie ostatniego procentu przewijania zamiast wartości bezwzględnej, co powoduje wolniejsze zanikanie. Oryginalne zachowanie jest bardziej podobne do Facebooka, ale to też mi się podoba.

Uwaga: to rozwiązanie dotyczy tylko systemu iOS 7+. Pamiętaj, aby dodać niezbędne kontrole, jeśli obsługujesz starsze wersje iOS.

Wayne
źródło
To działa. Z wyjątkiem zakładania, że ​​elementy przycisków paska mają niestandardowe widoki. W moim przypadku nie mam niestandardowego widoku. Więc powyższe nie ukrywa przycisków paska. Myślę, że rozwiązanie @peerless jest lepsze do ukrywania i pokazywania elementów paska nawigacyjnego
Dhanush
1
Masz rację. W ten weekend mogę przyjrzeć się bardziej ogólnemu rozwiązaniu. Nie powinno być zbyt trudne wycelowanie w domyślne elementy zamiast customView.
Wayne,
Nie rozwiązałem problemu @ Dhanush, ale mam aktualizację.
Wayne,
1
Naprawiłem problem z przyciskami paska zapasów, ale ten kod ma inny problem. Jeżeli ScrollViewjest contentSizemniejszy niż ramka, animacja przesuwnych nie działa. Upewnij się również, że zresetowałeś alfa wszystkich elementów nawigacyjnych z powrotem do 1,0 cala viewDidDisappear.
Bezprawny
1
Czy sprawdziłeś to rozwiązanie na kontrolerach, które używają Autoukładu? W moim przypadku wszystko działa dobrze bez AutoLayout, ale po włączeniu nadal widać pod nim dziwny biały pasek
Aliaksandr B.
52

EDYCJA: tylko dla iOS 8 i nowszych.

Możesz spróbować użyć

self.navigationController.hidesBarsOnSwipe = YES;

Pracuje dla mnie.

Jeśli kodujesz szybko, musisz użyć tego sposobu (z https://stackoverflow.com/a/27662702/2283308 )

navigationController?.hidesBarsOnSwipe = true
Pedro Romão
źródło
to nie jest dostępne w iOS <8.0
Petar
1
Jak powiedział pet60t0 i przepraszam, działa tylko na iOS 8 i nowszych.
Pedro Romão
4
Jak potem to przywracasz?
C0D3
zachowanie jest trochę dziwne. Nie eksplorowałem zbyt wiele, ale jeśli przewiniesz szybciej, pojawi się ponownie.
Pedro Romão
@ PedroRomão świetna odpowiedź.
Gagan_iOS
43

Oto jeszcze jedna implementacja: TLYShyNavBar v1.0.0 wydany!

Postanowiłem zrobić własne po wypróbowaniu dostarczonych rozwiązań i według mnie albo działały one słabo, miały wysoką barierę wejścia i kod płyty kotła, albo brakowały widoku przedłużenia poniżej paska nawigacyjnego. Aby skorzystać z tego komponentu, wystarczy:

self.shyNavBarManager.scrollView = self.scrollView;

Aha, i jest to test bojowy w naszej własnej aplikacji.

Mazyod
źródło
@TimArnold Dziękujemy za Twoją opinię! Naprawiłem ten problem, po prostu nie zaktualizowałem jeszcze kapsuły> _ <zrobi to teraz! .. Pod zaktualizowany!
Mazyod
Dam mu kolejny strzał! Dzięki!
Tim Arnold,
Doceniam twoją pomoc. Wygląda na to, że twoje zatwierdzenie pomogło. Nadal pojawia się dziwny problem polegający na tym, że mój UICollectionView nie jest prawidłowo zmieniany tak, jak pasek nawigacyjny, a więc komórki przechodzące w górę w miejscu, w którym używany jest pasek nawigacji, są przycinane, ponieważ znajdują się poza granicami widoku kolekcji. Czy wiesz, dlaczego tak się dzieje?
Tim Arnold,
4
Liczby: znalazłem swoje rozwiązanie kilka sekund po napisaniu tego komentarza. Musiałem się upewnić, że jestem extendedLayoutIncludesOpaqueBarsustawiony YESna moimUICollectionViewController
Tim Arnold.
@TimArnold To niesamowite! Był inny facet, który miał ten sam problem i miejmy nadzieję, że twoje rozwiązanie mu pomoże.
Mazyod
33

Możesz rzucić okiem na mój GTScrollNavigationBar . Mam podklasę UINavigationBar, aby przewijała się na podstawie przewijania UIScrollView.

Uwaga: jeśli masz OPAQUE pasek nawigacji, widok przewijania musi ROZWIJAĆ się, ponieważ pasek nawigacji staje się UKRYTY. To jest dokładnie to, co robi GTScrollNavigationBar. (Tak jak na przykład w Safari na iOS).

Czw
źródło
BTW dla każdego, kto czyta, dokładnie jak wywołać initWithNavigationBarClass ... stackoverflow.com/questions/22286166
Fattie
@Thuy, świetna robota! Więc mam to doskonale działające na kontrolerze widoku tabeli z wyjątkiem jednej rzeczy ... Muszę odświeżyć zaimplementowane na górze. Robi się dziwnie, gdy próbujesz ściągnąć i odświeżyć. Czy może istnieć obejście tego problemu?
aherrick
@Thuy także ... powiedzmy, że miałem kontroler widoku, w którym zaimplementowałem widok tabeli na dole. Chcę podłączyć się do tego widoku tabeli, który działa, ale mam inny widok, który znajduje się nad widokiem tabeli, który również chcę zniknąć. Jak to może działać?
aherrick
25

iOS8 zawiera właściwości umożliwiające bezpłatne ukrywanie paska nawigacji. Jest wideo WWDC, które to demonstruje, wyszukaj „Zobacz postępy kontrolera w iOS 8”.

Przykład :

class QuotesTableViewController: UITableViewController {

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    navigationController?.hidesBarsOnSwipe = true
}

}

Pozostałe właściwości:

class UINavigationController : UIViewController {

    //... truncated

    /// When the keyboard appears, the navigation controller's navigationBar toolbar will be hidden. The bars will remain hidden when the keyboard dismisses, but a tap in the content area will show them.
    @availability(iOS, introduced=8.0)
    var hidesBarsWhenKeyboardAppears: Bool
    /// When the user swipes, the navigation controller's navigationBar & toolbar will be hidden (on a swipe up) or shown (on a swipe down). The toolbar only participates if it has items.
    @availability(iOS, introduced=8.0)
    var hidesBarsOnSwipe: Bool
    /// The gesture recognizer that triggers if the bars will hide or show due to a swipe. Do not change the delegate or attempt to replace this gesture by overriding this method.
    @availability(iOS, introduced=8.0)
    var barHideOnSwipeGestureRecognizer: UIPanGestureRecognizer { get }
    /// When the UINavigationController's vertical size class is compact, hide the UINavigationBar and UIToolbar. Unhandled taps in the regions that would normally be occupied by these bars will reveal the bars.
    @availability(iOS, introduced=8.0)
    var hidesBarsWhenVerticallyCompact: Bool
    /// When the user taps, the navigation controller's navigationBar & toolbar will be hidden or shown, depending on the hidden state of the navigationBar. The toolbar will only be shown if it has items to display.
    @availability(iOS, introduced=8.0)
    var hidesBarsOnTap: Bool
    /// The gesture recognizer used to recognize if the bars will hide or show due to a tap in content. Do not change the delegate or attempt to replace this gesture by overriding this method.
    @availability(iOS, introduced=8.0)
    unowned(unsafe) var barHideOnTapGestureRecognizer: UITapGestureRecognizer { get }
}

Znalezione przez http://natashatherobot.com/navigation-bar-interactions-ios8/

Michael Peterson
źródło
12

Mam na to szybkie i brudne rozwiązanie. Nie przeprowadziłem żadnych szczegółowych testów, ale oto pomysł:

Ta właściwość zachowa wszystkie elementy na pasku nawigacyjnym dla mojej klasy UITableViewController

@property (strong, nonatomic) NSArray *navBarItems;

W tej samej klasie UITableViewController mam:

-(void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{
    if([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0f){
        return;
    }

    CGRect frame = self.navigationController.navigationBar.frame;
    frame.origin.y = 20;

    if(self.navBarItems.count > 0){
        [self.navigationController.navigationBar setItems:self.navBarItems];
    }

    [self.navigationController.navigationBar setFrame:frame];
}

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0f){
        return;
    }

    CGRect frame = self.navigationController.navigationBar.frame;
    CGFloat size = frame.size.height - 21;

    if([scrollView.panGestureRecognizer translationInView:self.view].y < 0)
    {
        frame.origin.y = -size;

        if(self.navigationController.navigationBar.items.count > 0){
            self.navBarItems = [self.navigationController.navigationBar.items copy];
            [self.navigationController.navigationBar setItems:nil];
        }
    }
    else if([scrollView.panGestureRecognizer translationInView:self.view].y > 0)
    {
        frame.origin.y = 20;

        if(self.navBarItems.count > 0){
            [self.navigationController.navigationBar setItems:self.navBarItems];
        }
    }

    [UIView beginAnimations:@"toggleNavBar" context:nil];
    [UIView setAnimationDuration:0.2];
    [self.navigationController.navigationBar setFrame:frame];
    [UIView commitAnimations];
}

To tylko dla ios> = 7, wiem, że to brzydkie, ale szybki sposób na osiągnięcie tego. Wszelkie uwagi / sugestie są mile widziane :)

niezrównany
źródło
12

Działa to na iOS 8 i nowszych wersjach i zapewnia, że ​​pasek stanu nadal zachowuje swoje tło

self.navigationController.hidesBarsOnSwipe = YES;
CGRect statuBarFrame = [UIApplication sharedApplication].statusBarFrame;
UIView *statusbarBg = [[UIView alloc] initWithFrame:statuBarFrame];
statusbarBg.backgroundColor = [UIColor blackColor];
[self.navigationController.view addSubview:statusbarBg];

A jeśli chcesz wyświetlić pasek nawigacyjny po dotknięciu paska stanu, możesz to zrobić:

- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
     self.navigationController.navigationBarHidden = NO;
}
Zhong Huiwen
źródło
10

Oto moja implementacja: SherginScrollableNavigationBar .

W moim podejściu używam KVOdo obserwacji UIScrollViewstanu, więc nie ma potrzeby korzystania z delegata (i możesz go używać do wszystkiego, czego potrzebujesz).

Valentin Shergin
źródło
FYI to nie zawsze działa poprawnie. Też próbowałem i działa tak długo, jak długo nie „odbijasz” widoku scroll. Wygląda na to, że KVO nie jest wyzwalane w części odbijającej. Jednak wywołanie delegata dla contentOffset jest wyzwalane.
Joris Mans
7

Wypróbuj to moje rozwiązanie i daj mi znać, dlaczego nie jest tak dobre, jak poprzednie odpowiedzi.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    if (fabs(velocity.y) > 1)
        [self hideTopBar:(velocity.y > 0)];
}

- (void)hideTopBar:(BOOL)hide
{
    [self.navigationController setNavigationBarHidden:hide animated:YES];
    [[UIApplication sharedApplication] setStatusBarHidden:hide withAnimation:UIStatusBarAnimationSlide];
}
Nishant
źródło
1
Zrobiłem coś podobnego i to jest bez wątpienia najlepsze rozwiązanie. Wypróbowałem kilka hacków i bibliotek i jest to jedyna, która działa na iOS 9 z tableView nie obejmującym całego ekranu. Sława!
skensell
6

Oto jeden ze sposobów, w jaki to osiągnąłem.

Zarejestruj swój kontroler widoku, aby być UIScrollViewDelegateswoim UITableViewna przykład.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;

Z poziomu UIScrollViewDelegatemetod możesz pobrać nową zawartość i odpowiednio przetłumaczyć UINavigationBarją w górę lub w dół.

Ustawienie alfa widoków podrzędnych można również wykonać na podstawie pewnych wartości progowych i współczynników, które można ustawić i obliczyć.

Mam nadzieję, że to pomoże!

Diana Sule
źródło
Znalazłem podobny post tutaj
Diana Sule,
Dzięki Diana! Podejrzewałem, że być może będę musiał zaimplementować metody UIScrollViewDelegate, ale wydawało się, że może to być trochę przesada. Kiedy to wymyślę, opublikuję to. Twoje zdrowie!
El Mocoso
4

Oprócz odpowiedzi Iwburka dodałem następujące, aby naprawić problem alfa na niestandardowych paskach nawigacyjnych i zresetować pasek nawigacji w metodzie viewWillDisappear:

- (void)updateBarButtonItems:(CGFloat)alpha
{
    for (UIView *view in self.navigationController.navigationBar.subviews) {
        NSString *className = NSStringFromClass([view class]);

        if ( ![className isEqualToString:@"_UINavigationBarBackground"] ) {
            view.alpha = alpha;
        }
    }
}

- (void)resetNavigationBar {
    CGRect frame = self.navigationController.navigationBar.frame;
    frame.origin.y = 20;
    [self.navigationController.navigationBar setFrame:frame];
    [self updateBarButtonItems:1.0f];
}
niebieski lód
źródło
Czy jest jakiś inny sposób niż przeglądanie podglądów podrzędnych?
thisiscrazy4
Z tego, co mogłem powiedzieć na niestandardowym pasku nawigacyjnym, są łącznie 4 podglądy: _UINavigationBarBackground, UINavigationItemView, UINavigationItemButtonView i _UINavigationBarBackIndicatorView. Pętla jest dość szybka i nie wydaje się wpływać na wydajność mojej aplikacji.
blueice
Ponieważ _UINavigationBarBackground wydaje się zawsze być pierwszym podziałem, do reszty można uzyskać bezpośredni dostęp: ((UIView *) self.navigationController.navigationBar.subviews [1]). Alpha = alpha;
blueice
4

Szukałem rozwiązania, które pozwoli na dowolny styl i dowolne zachowanie. Zauważysz, że zachowanie skraplania paska jest inne w wielu różnych aplikacjach. Oczywiście wygląd paska jest zupełnie inny w różnych aplikacjach.

Stworzyłem rozwiązanie tego problemu z https://github.com/bryankeller/BLKF flexibleHeightBar/

Możesz zdefiniować własne reguły zachowania, aby kontrolować, jak i kiedy pasek kurczy się i rośnie, a także możesz dokładnie określić, w jaki sposób podglądy słupka mają reagować na kondensację lub wzrost słupka.

Spójrz na mój projekt, jeśli chcesz mieć dużą elastyczność w tworzeniu dowolnego rodzaju paska nagłówka, jaki możesz wymyślić.

blkhp19
źródło
Jak mogę dodać przycisk do tego customHeaderView, który będzie się ukrywał po przewinięciu w górę. Nie potrzebuję statycznego przycisku. Czy to możliwe? Próbowałem utworzyć jeden przycisk jako widok podrzędny, ale nie otrzymuje on żadnych dotknięć.
abhimuralidharan
3

Próbowałem naśladować to zachowanie w sytuacji, w której potrzebowałem niestandardowego nagłówka siedzącego wokół UITableView. Założyłem własny pasek „nawigacji”, ponieważ znajduje się on poniżej kilku innych elementów na stronie i chciałem, aby nagłówki sekcji były zgodne z domyślnym zachowaniem „dokowania”. Myślę, że znalazłem całkiem sprytny i zwięzły sposób na dostosowanie UITableView / UIScrollView razem z innym obiektem w stylu podobnym do tego, który widać na Facebooku / Instagramie / Chrome / itp. aplikacje.

W moim pliku .xib moje komponenty są załadowane do widoku swobodnego: http://imgur.com/0z9yebJ (przepraszam, nie mam rep do wbudowanych obrazów)

Zwróć uwagę, że na lewym pasku bocznym tabela jest uporządkowana za głównym widokiem nagłówka. Nie widać tego na zrzucie ekranu, ale ma również tę samą pozycję y, co główny widok nagłówka. Ponieważ rozszerza się poza zasięg wzroku, właściwość contentInset w UITableView jest ustawiona na 76 (wysokość głównego widoku nagłówka).

Aby główny widok nagłówka przesuwał się w górę zgodnie z UIScrollView, używam metod scrollViewDidScroll UIScrollViewDelegate do wykonywania niektórych obliczeń i zmiany contentInset UIScrollView, a także ramki widoku głównego nagłówka.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    UIEdgeInsets insets = scrollView.contentInset;
    //tableViewInsetDelta and tableViewOriginalInsetValue are NSInteger variables that I set to 0 and 76, respectively, in viewDidLoad
    tableViewInsetDelta = tableViewOriginalInsetValue + scrollView.contentOffset.y;
    insets.top = tableViewOriginalInsetValue - tableViewInsetDelta;

    if (scrollView.contentOffset.y > -76 && scrollView.contentOffset.y < 0) {
        [scrollView setContentInset:insets];
        self.pathTitleContainer.frame = CGRectMake(self.pathTitleContainer.frame.origin.x, 44 - tableViewInsetDelta, self.pathTitleContainer.frame.size.width, self.pathTitleContainer.frame.size.height);
    } else if (scrollView.contentOffset.y > 0) {
        insets.top = 0;
        [scrollView setContentInset:insets];
        self.pathTitleContainer.frame = CGRectMake(self.pathTitleContainer.frame.origin.x, -32, self.pathTitleContainer.frame.size.width, self.pathTitleContainer.frame.size.height);
    } else if (scrollView.contentOffset.y < -76) {
        insets.top = 76;
        [scrollView setContentInset:insets];
        self.pathTitleContainer.frame = CGRectMake(self.pathTitleContainer.frame.origin.x, 44, self.pathTitleContainer.frame.size.width, self.pathTitleContainer.frame.size.height);
    }
}

Pierwsza instrukcja if wykonuje większość ciężkiego podnoszenia, ale musiałem uwzględnić dwie pozostałe, aby obsłużyć sytuacje, w których użytkownik przeciąga na siłę, a początkowe wartości contentOffset wysyłane do scrollViewDidScroll są poza zakresem pierwszego if instrukcji .

Ostatecznie to działa dla mnie naprawdę dobrze. Nienawidzę ładować moich projektów masą nadętych podklas. Nie mogę mówić, czy jest to najlepsze rozwiązanie pod względem wydajności (zawsze wahałem się przed umieszczeniem dowolnego kodu w scrollViewDidScroll, ponieważ jest on wywoływany przez cały czas), ale ślad kodu jest najmniejszy, jaki widziałem w jakimkolwiek rozwiązanie tego problemu i nie obejmuje zagnieżdżania UITableView w UIScrollView (Apple odradza to w dokumentacji, a zdarzenia dotykowe kończą się nieco dziwnie w UITableView). Mam nadzieję, że to komuś pomoże!

Brian
źródło
2

Próbowałem zaimplementować GTScrollNavigationBar, ale moja aplikacja wymagała ode mnie modyfikacji ograniczeń układu automatycznego. Postanowiłem umieścić przykład mojej implementacji na GitHubie na wypadek, gdyby ktoś inny musiał to zrobić z automatycznym układem. Innym problemem, który miałem z większością innych implementacji, jest to, że ludzie nie ustawiają granic widoku przewijania, aby uniknąć efektu przewijania paralaksy, który tworzysz podczas jednoczesnego przewijania i dostosowywania rozmiaru przewijania.

Sprawdź JSCollapsingNavBarViewController, jeśli chcesz to zrobić z automatycznym układem. Dołączyłem dwie wersje, jedną z samym paskiem nawigacyjnym, a drugą z paskiem pomocniczym poniżej paska nawigacji, który zwija się przed zwinięciem paska nawigacji.

jwswart
źródło
1

próbowałem tego w ten sposób, mam nadzieję, że to pomoże. po prostu zaimplementuj kod w metodzie delegata i ustaw na żądany widok / podwidok

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{ 
            CGRect frame=self.view.frame;
            CGRect resultFrame=CGRectZero;
            if(scrollView.contentOffset.y==0 || scrollView.contentOffset.y<0){
                self.lastContentOffset=0;
                self.offset=0;
                resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue);
    // Pass the resultFrame
                [self showHide:YES withFrame:resultFrame];
            }else if (self.lastContentOffset > scrollView.contentOffset.y){
                NSNumber *temp=[NSNumber numberWithDouble:self.lastContentOffset-scrollView.contentOffset.y];
                if(temp.intValue>40 || self.offset.intValue<temp.intValue){
                    self.offset=[NSNumber numberWithInt:0];
                    resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue);
    // Pass the resultFrame
                    [self showHide:YES withFrame:resultFrame];
                }else{
                    if(temp.intValue>0){
                        self.offset=[NSNumber numberWithInt:self.offset.intValue-temp.intValue];
                        resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue);
    // Pass the resultFrame
                        [self showHide:YES withFrame:resultFrame];
                    }
                }
            }else if (self.lastContentOffset < scrollView.contentOffset.y){
                NSNumber *temp=[NSNumber numberWithDouble:scrollView.contentOffset.y-self.lastContentOffset];
                if(self.offset.intValue>40 || (self.offset.intValue+temp.intValue)>40){
                    self.offset=[NSNumber numberWithInt:40];
    // Pass the resultFrame
                    [self showHide:NO withFrame:resultFrame];
                }else{
                    self.offset=[NSNumber numberWithInt:self.offset.intValue+temp.intValue];
                    resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue);
    // Pass the resultFrame
                    [self showHide:YES withFrame:resultFrame];
                }
            }
            self.lastContentOffset = scrollView.contentOffset.y;

        }

-(void)showHide:(Boolean)boolView withFrame:(CGRect)frame{
               if(showSRPFilter){
                        //Assign value of "frame"to any view on which you wan to to perform animation
                }else{
                       //Assign value of "frame"to any view on which you wan to to perform animation
                }
        }
user2968901
źródło
1

Rozszerzenie odpowiedzi @Iwburk ... Zamiast zmieniać początek paska nawigacji, musiałem rozszerzyć / zmniejszyć rozmiar paska nawigacji.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGRect frame = self.previousRect; // a property set in the init method to hold the initial size of the uinavigationbar
    CGFloat size = frame.size.height;
    CGFloat framePercentageHidden = ((MINIMUMNAVBARHEIGHT - frame.origin.y) / (frame.size.height - 1));
    CGFloat scrollOffset = scrollView.contentOffset.y;
    CGFloat scrollDiff = scrollOffset - self.previousScrollViewYOffset;
    CGFloat scrollHeight = scrollView.frame.size.height;
    CGFloat scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom;

    if (scrollOffset <= -scrollView.contentInset.top) {
        frame.origin.y = -MINIMUMNAVBARHEIGHT;
    } else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) {
        frame.origin.y = -size;
    } else {
        frame.origin.y = MIN(-MINIMUMNAVBARHEIGHT, MAX(-size, frame.origin.y - scrollDiff));
    }

    self.previousRect = CGRectMake(0, frame.origin.y, self.jsExtendedBarView.frame.size.width, 155);
    self.layoutConstraintExtendedViewHeight.constant = MAXIMUMNAVBARHEIGHT + frame.origin.y + MINIMUMNAVBARHEIGHT;
    [self updateBarButtonItems:(1 - framePercentageHidden)];
    self.previousScrollViewYOffset = scrollOffset;
}

To jeszcze nie działa z tą stoppedScrollingmetodą, opublikuję aktualizację, gdy ją mam

jsetting32
źródło
0

Wszystkie te podejścia wydają się zbyt skomplikowane ... Więc naturalnie zbudowałem własne:

class ViewController: UIViewController, UIScrollViewDelegate {
    var originalNavbarHeight:CGFloat = 0.0
    var minimumNavbarHeight:CGFloat = 0
    weak var scrollView:UIScrollView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // setup delegates 
        scrollView.delegate = self
        // save the original nav bar height
        originalNavbarHeight = navigationController!.navigationBar.height
    }


    func scrollViewDidScroll(scrollView: UIScrollView) {
        // will relayout subviews
        view.setNeedsLayout() // calls viewDidLayoutSubviews
    }

    override func viewDidLayoutSubviews() {
        var percentageScrolled = min(scrollView.contentOffset.y / originalNavbarHeight, 1)
        navigationController?.navigationBar.height = min(max((1 - percentageScrolled) * originalNavbarHeight, minimumNavbarHeight), originalNavbarHeight)
        // re-position and scale scrollview
        scrollView.y = navigationController!.navigationBar.height + UIApplication.sharedApplication().statusBarFrame.height
        scrollView.height = view.height - scrollView.y
    }

    override func viewWillDisappear(animated: Bool) {
        navigationController?.navigationBar.height = originalNavbarHeight
    }

}
Oxcug
źródło
navigationBar.height? scrollView.height? Czy używasz przedłużenia na frameposesji?
Ely
0

Znalazłem wszystkie odpowiedzi podane w Objective-C. To jest moja odpowiedź w Swift 3. To jest bardzo ogólny kod i może być używany bezpośrednio. Działa zarówno z UIScrollView, jak i UITableView.

var lastContentOffset: CGPoint? = nil
var maxMinus: CGFloat           = -24.0
var maxPlus: CGFloat            = 20.0
var initial: CGFloat            = 0.0

override func viewDidLoad() {
    super.viewDidLoad()

    self.title = "Alarm Details"
    self.lastContentOffset = self.alarmDetailsTableView.contentOffset
    initial = maxPlus
}

func scrollViewDidScroll(_ scrollView: UIScrollView)
{
    var navigationBarFrame: CGRect   = self.navigationController!.navigationBar.frame
    let currentOffset = scrollView.contentOffset

    if (currentOffset.y > (self.lastContentOffset?.y)!) {
        if currentOffset.y > 0 {
            initial = initial - fabs(CGFloat(currentOffset.y - self.lastContentOffset!.y))
        }
        else if scrollView.contentSize.height < scrollView.frame.size.height {
            initial = initial + fabs(CGFloat(currentOffset.y - self.lastContentOffset!.y))
        }
    }
    else {
        if currentOffset.y < scrollView.contentSize.height - scrollView.frame.size.height {
            initial = initial + fabs(CGFloat(currentOffset.y - self.lastContentOffset!.y))
        }
        else if scrollView.contentSize.height < scrollView.frame.size.height && initial < maxPlus {
            initial = initial - fabs(CGFloat(currentOffset.y - self.lastContentOffset!.y))
        }
    }

    initial = (initial <= maxMinus) ? maxMinus : initial
    initial = (initial >= maxPlus) ? maxPlus : initial

    navigationBarFrame.origin.y = initial

    self.navigationController!.navigationBar.frame = navigationBarFrame
    scrollView.frame = CGRect(x: 0.0, y: initial + navigationBarFrame.size.height , width: navigationBarFrame.size.width, height: self.view.frame.size.height - (initial + navigationBarFrame.size.height))

    let framePercentageHidden: CGFloat              = ((20 - navigationBarFrame.origin.y) / (navigationBarFrame.size.height));
    self.lastContentOffset                          = currentOffset;
    self.updateBarButtonItems(alpha: 1 - framePercentageHidden)
}

func updateBarButtonItems(alpha: CGFloat)
{
    self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.darkGray.withAlphaComponent(alpha)]
    self.navigationController?.navigationBar.isUserInteractionEnabled = (alpha < 1) ? false: true

    guard (self.navigationItem.leftBarButtonItems?.count) != nil else { return }

    for (_, value) in self.navigationItem.leftBarButtonItems!.enumerated() {
        value.customView?.alpha = alpha
    }

    guard (self.navigationItem.rightBarButtonItems?.count) != nil else { return }

    for (_, value) in (self.navigationItem.rightBarButtonItems?.enumerated())! {
        value.customView?.alpha = alpha
    }
}

Logika ustawiania alfa do elementów nawigacyjnych jest kopiowana z odpowiedzi @ WayneBurkett i przepisywana w Swift 3.

Dev
źródło
0

dla Swift 4,5 - iOS 11 i nowszych

private var previousScrollViewYOffset: CGFloat = 0
private var firstLoad = true
// to avoid scrollViewDidScroll called when first time view controller load
override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        firstLoad = false
    }
// MARK: - UIScrollViewDelegate
extension ViewController: UIScrollViewDelegate {
    func stoppedScrolling() {
        let frame = self.navigationController?.navigationBar.frame ?? .zero
        if frame.origin.y < UIView.statusBarFrame.size.height {
            self.animateNavBar(to: -frame.size.height + UIView.statusBarFrame.size.height)
        }
    }
    func updateBarButtonItems(alpha: CGFloat) {
        self.navigationItem.leftBarButtonItems?.forEach{ item in
            item.customView?.alpha = alpha
        }
        self.navigationItem.rightBarButtonItems?.forEach{ item in
            item.customView?.alpha = alpha
        }
        self.navigationItem.titleView?.alpha = alpha
        self.navigationController?.navigationBar.tintColor = self.navigationController?.navigationBar.tintColor.withAlphaComponent(alpha)
    }
    
    func animateNavBar(to y: CGFloat) {
        UIView.animate(withDuration: 0.2) {[weak self] in
            var frame: CGRect = self?.navigationController?.navigationBar.frame ?? .zero
            let alpha: CGFloat = frame.origin.y >= y ? 0 : 1
            frame.origin.y = y
            self?.navigationController?.navigationBar.frame = frame
            self?.updateBarButtonItems(alpha: alpha)
        }
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if firstLoad { return }
        var frame = self.navigationController?.navigationBar.frame ?? .zero
        let size = frame.size.height - UIView.statusBarFrame.size.height
        let framePercentageHidden = (UIView.statusBarFrame.size.height - frame.origin.y) / (frame.size.height - 1)
        let scrollOffset = scrollView.contentOffset.y
        let scrollDiff = scrollOffset - previousScrollViewYOffset
        let scrollHeight = scrollView.frame.size.height
        let scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom
        if scrollOffset <= -scrollView.contentInset.top {
            frame.origin.y = UIView.statusBarFrame.size.height
        } else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) {
            frame.origin.y = -size
        } else {
            frame.origin.y = min(UIView.statusBarFrame.size.height, max(-size, frame.origin.y - scrollDiff))
        }
        self.navigationController?.navigationBar.frame = frame
        self.updateBarButtonItems(alpha: 1 - framePercentageHidden)
        self.previousScrollViewYOffset = scrollOffset
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        self.stoppedScrolling()
    }
    
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if(!decelerate) {
            self.stoppedScrolling()
        }
    }
}

Rozszerzenie UIView

extension UIView {
    public static var statusBarFrame: CGRect {
        get {
            return UIApplication.shared.statusBarFrame
        }
    }
}

Powinieneś navigationItem.titleViewustawić niestandardowy zestawalpha

Giang
źródło