Imitieren Sie das Ausblenden / Anzeigen der Navigationsleiste auf Facebook


128

In der neuen Facebook-App für iOS7 Facebook navigationBarversteckt sich der Benutzer beim Scrollen nach und nach bis zu einem Punkt, an dem er vollständig verschwindet. Wenn der Benutzer dann navigationBarnach unten scrollt, zeigt sich das allmählich.

Wie würden Sie dieses Verhalten selbst implementieren? Mir ist die folgende Lösung bekannt, aber sie verschwindet sofort und ist überhaupt nicht an die Geschwindigkeit der Bildlaufgeste des Benutzers gebunden.

[navigationController setNavigationBarHidden: YES animated:YES];

Ich hoffe, dass dies kein Duplikat ist, da ich nicht sicher bin, wie ich das Verhalten beim "Erweitern / Zusammenziehen" am besten beschreiben kann.


2
Gleiche Probleme: stackoverflow.com/questions/21929220/… Beachten Sie, dass es unglaublich schwierig ist , das Safari-Verhalten absolut anzupassen . Es gibt einige sehr, sehr komplizierte Regeln!
Fattie

1
In meinem Projekt habe ich dieses Projekt verwendet und es hat gut funktioniert. Schauen Sie sich die Dokumentation an.
Vinicius

Mit github.com/bryankeller/BLKFlexibleHeightBar können Sie tun, was Sie wollen und mehr. Hier können Sie genau festlegen, wie der Balken in jeder Phase seines Übergangs von maximiert zu minimiert aussieht. Sie können sogar Ihr eigenes Verhalten festlegen, sodass es sich wie Safari, Facebook oder eine andere App verhalten kann.
blkhp19

Ich habe keine uinavigationbar verwendet, sondern stattdessen eine uiview hinzugefügt. Die Ansicht, die die Navigationsleiste repliziert, wird basierend auf dem Bildlauf erweitert und verkleinert. Ich habe die delegate-Methode scrollViewDidScroll verwendet, um die Aufgabe zu erfüllen. Vielleicht möchten Sie den folgenden Quellcode überprüfen und ausführen. Dropbox.com/s/b2c0zw6yvchaia5/FailedBanks.zip?dl=0
Deepak Thakur

Antworten:


162

Die von @peerless gegebene Lösung ist ein guter Anfang, startet jedoch nur dann eine Animation, wenn das Ziehen beginnt, ohne die Geschwindigkeit des Bildlaufs zu berücksichtigen. Dies führt zu einer abgehackteren Erfahrung als in der Facebook-App. Um dem Verhalten von Facebook zu entsprechen, müssen wir:

  • Blenden Sie die Navigationsleiste mit einer Geschwindigkeit aus, die proportional zur Widerstandsgeschwindigkeit ist
  • Starten Sie eine Animation, um die Leiste vollständig auszublenden, wenn das Scrollen stoppt, wenn die Leiste teilweise ausgeblendet ist
  • Verblassen Sie die Elemente der Navigationsleiste, wenn die Leiste kleiner wird.

Zunächst benötigen Sie die folgende Eigenschaft:

@property (nonatomic) CGFloat previousScrollViewYOffset;

Und hier sind die UIScrollViewDelegateMethoden:

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

Sie benötigen außerdem die folgenden Hilfsmethoden:

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

Ersetzen Sie für ein etwas anderes Verhalten die Zeile, die die Leiste beim Scrollen neu positioniert (den elseBlock in scrollViewDidScroll), durch diese:

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

Dadurch wird der Balken basierend auf dem letzten Bildlaufprozentsatz anstelle eines absoluten Betrags positioniert, was zu einer langsameren Überblendung führt. Das ursprüngliche Verhalten ist eher Facebook-ähnlich, aber ich mag dieses auch.

Hinweis: Diese Lösung ist nur für iOS 7+ verfügbar. Stellen Sie sicher, dass Sie die erforderlichen Überprüfungen hinzufügen, wenn Sie ältere Versionen von iOS unterstützen.


Das funktioniert. Es sei denn, Sie gehen davon aus, dass die Balkenschaltflächenelemente benutzerdefinierte Ansichten haben. In meinem Fall habe ich keine benutzerdefinierte Ansicht. Das Obige verbirgt also nicht die Balkentasten. Ich denke, @peerless Lösung ist besser zum Verstecken und Anzeigen von Navbar-Elementen
Dhanush

1
Du hast recht. Ich werde dieses Wochenende vielleicht eine allgemeinere Lösung suchen. Sollte nicht zu schwierig sein, stattdessen auf die Standardelemente zu zielen customView.
Wayne

Ich habe das Problem von @ Dhanush nicht angesprochen, aber ich habe ein Update.
Wayne

1
Ich habe das Problem mit den Schaltflächen der Lagerleiste behoben, aber dieser Code hat ein anderes Problem. Wenn ScrollView's contentSizekleiner als der Rahmen ist, funktioniert die Animation des Gleitens nicht. Stellen Sie außerdem sicher, dass Sie das Alpha aller Navigationselemente auf 1,0 Zoll zurücksetzen viewDidDisappear.
Legoless

1
Haben Sie diese Lösung auf Controllern überprüft, die AutoLayout verwenden? In meinem Fall funktioniert alles gut ohne AutoLayout, aber wenn es sich einschaltet, ist immer noch ein seltsamer weißer Streifen darunter sichtbar
Aliaksandr B.

52

BEARBEITEN: Nur für iOS 8 und höher.

Sie können versuchen, zu verwenden

self.navigationController.hidesBarsOnSwipe = YES;

Funktioniert bei mir.

Wenn Ihre Codierung schnell ist, müssen Sie diese Methode verwenden (von https://stackoverflow.com/a/27662702/2283308 ).

navigationController?.hidesBarsOnSwipe = true

Dies ist nicht verfügbar in iOS <8.0
Petar

1
Wie pet60t0 sagte, und ich entschuldige mich, funktioniert nur für iOS 8 und höher.
Pedro Romão

4
Wie bringst du es danach zurück?
C0D3

Das Verhalten ist etwas seltsam. Ich habe nicht viel erforscht, aber wenn Sie schneller scrollen, wird es wieder angezeigt.
Pedro Romão

@ PedroRomão tolle Antwort.
Gagan_iOS

43

Hier ist noch eine Implementierung: TLYShyNavBar v1.0.0 veröffentlicht!

Nachdem ich die angebotenen Lösungen ausprobiert hatte, entschloss ich mich, meine eigenen zu machen, und für mich waren sie entweder schlecht, hatten eine hohe Eintrittsbarriere und einen Code für die Kesselplatte oder es fehlte die Erweiterungsansicht unter der Navigationsleiste. Um diese Komponente zu verwenden, müssen Sie lediglich Folgendes tun:

self.shyNavBarManager.scrollView = self.scrollView;

Oh, und es ist kampferprobt in unserer eigenen App.


@ TimArnold Danke für dein Feedback! Ich habe dieses Problem behoben und den Pod noch nicht aktualisiert.> _ <Wird das jetzt tun! .. Pod aktualisiert!
Mazyod

Ich werde es noch einmal versuchen! Vielen Dank!
Tim Camber

Ich schätze Ihre Hilfe. Sieht aus wie Ihr Commit geholfen hat. Ich erhalte immer noch ein seltsames Problem, bei dem die Größe meiner UICollectionView nicht richtig geändert wird, wie es bei der Navigationsleiste der Fall ist. Daher werden Zellen, bei denen die Navigationsleiste verwendet wird, abgeschnitten, da sie außerhalb der Grenzen der Sammlungsansicht liegen. Wissen Sie, warum dies passieren könnte?
Tim Camber

4
Zahlen: Ich habe meine Lösung Sekunden nach dem Schreiben dieses Kommentars gefunden. Ich musste sicherstellen extendedLayoutIncludesOpaqueBars, dass YESauf meinemUICollectionViewController
Tim Camber

@ TimArnold Das ist großartig! Es gab einen anderen Mann, der das gleiche Problem hatte , und hoffentlich hilft ihm Ihre Lösung.
Mazyod

33

Sie können sich meine GTScrollNavigationBar ansehen . Ich habe UINavigationBar in Unterklassen unterteilt, damit es basierend auf dem Scrollen einer UIScrollView einen Bildlauf durchführt.

Hinweis: Wenn Sie eine OPAQUE-Navigationsleiste haben, muss die Bildlaufansicht ERWEITERT werden, wenn die Navigationsleiste ausgeblendet wird. Genau das macht GTScrollNavigationBar. (Genau wie zum Beispiel in Safari unter iOS.)


Übrigens für jeden, der liest, genau wie man initWithNavigationBarClass aufruft ... stackoverflow.com/questions/22286166
Fattie

@Thuy tolle Arbeit Mann! Ich habe dies also perfekt auf einem Table View Controller ausgeführt, außer einer Sache ... Ich habe Pull to Refresh oben implementiert. Es wird komisch, wenn man versucht, sich herunterzuziehen und zu erfrischen. Könnte es eine Problemumgehung dafür geben?
aherrick

@Thuy auch ... Nehmen wir an, ich hatte einen View Controller, in dem ich unten eine Tabellenansicht implementiert habe. Ich möchte mich mit der Tabellenansicht verbinden, die funktioniert, aber ich habe eine andere Ansicht über der Tabellenansicht, die ich ebenfalls verschwinden lassen möchte. Wie könnte das funktionieren?
aherrick

25

iOS8 enthält Eigenschaften, mit denen die Navigationsleiste kostenlos ausgeblendet werden kann. Es gibt ein WWDC-Video, das dies demonstriert. Suchen Sie nach "View Controller Advancements in iOS 8".

Beispiel :

class QuotesTableViewController: UITableViewController {

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

    navigationController?.hidesBarsOnSwipe = true
}

}}

Andere Eigenschaften:

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 }
}

Gefunden über http://natashatherobot.com/navigation-bar-interactions-ios8/


12

Ich habe eine schnelle und schmutzige Lösung dafür. Ich habe noch keine eingehenden Tests durchgeführt, aber hier ist die Idee:

Diese Eigenschaft behält alle Elemente in der Navigationsleiste für meine UITableViewController-Klasse bei

@property (strong, nonatomic) NSArray *navBarItems;

In derselben UITableViewController-Klasse habe ich:

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

Das ist nur für ios> = 7, ich weiß, es ist hässlich, aber ein schneller Weg, dies zu erreichen. Kommentare / Vorschläge sind willkommen :)


12

Dies funktioniert für iOS 8 und höher und stellt sicher, dass die Statusleiste ihren Hintergrund beibehält

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

Und wenn Sie die Navigationsleiste anzeigen möchten, wenn Sie auf die Statusleiste tippen, können Sie dies tun:

- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
     self.navigationController.navigationBarHidden = NO;
}

10

Hier ist meine Implementierung: SherginScrollableNavigationBar .

In meinem Ansatz verwende ich KVOzur Beobachtung UIScrollViewdes Status, sodass keine Notwendigkeit besteht, einen Delegaten zu verwenden (und Sie können diesen Delegaten für alles verwenden, was Sie sonst noch benötigen).


Zu Ihrer Information, dies funktioniert nicht immer richtig. Ich habe es auch versucht und es funktioniert, solange Sie die Bildlaufansicht nicht "abprallen". Es scheint, dass KVO im Bounce-Teil nicht ausgelöst wird. Der Delegatenaufruf für das contentOffset wird jedoch ausgelöst.
Joris Mans

7

Bitte versuchen Sie meine Lösung und lassen Sie mich wissen, warum dies nicht so gut ist wie die vorherigen Antworten.

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

1
Ich habe etwas Ähnliches gemacht, und dies ist mit Sicherheit die beste Lösung. Ich habe mehrere Hacks und Bibliotheken ausprobiert, und dies ist die einzige, die für iOS 9 mit einer Tabellenansicht funktioniert, die nicht den gesamten Bildschirm abdeckt. Ein großes Lob!
Skensell

6

Eine Möglichkeit, dies zu erreichen, ist die folgende.

Registrieren Sie Ihren View Controller beispielsweise als UIScrollViewDelegateIhren UITableView.

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

Innerhalb der UIScrollViewDelegateMethoden können Sie das neue contentOffset abrufen und Ihr UINavigationBarUp oder Down entsprechend übersetzen.

Das Einstellen des Alphas der Unteransichten kann auch basierend auf einigen Schwellenwerten und Faktoren erfolgen, die Sie festlegen und berechnen können.

Ich hoffe es hilft!


Ähnlichen Beitrag hier gefunden
Diana Sule

Danke Diana! Ich vermutete, dass ich möglicherweise die UIScrollViewDelegate-Methoden implementieren musste, aber es schien, als wäre es ein wenig übertrieben. Sobald ich das herausgefunden habe, werde ich es veröffentlichen. Prost!
El Mocoso

4

Zusätzlich zu Iwburks Antwort habe ich Folgendes hinzugefügt, um das Alpha-Problem in nicht benutzerdefinierten Navigationsleisten zu beheben und die Navigationsleiste in der viewWillDisappear-Methode zurückzusetzen:

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

Gibt es eine andere Möglichkeit, als die Unteransichten zu durchlaufen?
thisiscrazy4

Nach dem, was ich in einer nicht benutzerdefinierten Navigationsleiste feststellen konnte, gibt es insgesamt 4 Unteransichten: _UINavigationBarBackground, UINavigationItemView, UINavigationItemButtonView und _UINavigationBarBackIndicatorView. Die Schleife ist ziemlich schnell und scheint die Leistung meiner App nicht zu beeinträchtigen.
Blueice

Da _UINavigationBarBackground immer die erste Unteransicht zu sein scheint, können Sie direkt auf den Rest zugreifen: ((UIView *) self.navigationController.navigationBar.subviews [1]). Alpha = alpha;
Blueice

4

Ich suchte nach einer Lösung, die jeden Stil und jedes Verhalten zuließ. Sie werden feststellen, dass das Verhalten der Balkenverdichtung in vielen verschiedenen Apps unterschiedlich ist. Und natürlich sieht die Leiste zwischen den Apps völlig anders aus.

Ich habe eine Lösung für dieses Problem mit https://github.com/bryankeller/BLKFlexibleHeightBar/ erstellt.

Sie können Ihre eigenen Verhaltensregeln definieren, um zu steuern, wie und wann der Balken schrumpft und wächst, und Sie können genau definieren, wie die Unteransichten des Balkens auf das Verdichten oder Wachsen des Balkens reagieren sollen.

Schauen Sie sich mein Projekt an, wenn Sie viel Flexibilität wünschen, um jede Art von Kopfzeile zu erstellen, die Sie sich vorstellen können.


Wie kann ich dieser customHeaderView eine Schaltfläche hinzufügen, die beim Scrollen nach oben ausgeblendet wird? Ich benötige keine statische Schaltfläche. Ist dies möglich? Ich habe versucht, eine Schaltfläche als Unteransicht zu erstellen. Sie erhält jedoch keine Berührungen.
Abhimuralidharan

3

Ich habe versucht, dieses Verhalten in einer Situation zu emulieren, in der ich einen benutzerdefinierten Header für eine UITableView benötigte. Ich habe meine eigene "Navigations" -Leiste gerollt, da sich diese unter einer Reihe anderer Elemente auf der Seite befindet und ich wollte, dass die Abschnittsüberschriften dem Standardverhalten "Andocken" folgen. Ich denke, ich habe eine ziemlich clevere und prägnante Möglichkeit gefunden, ein UITableView / UIScrollView zusammen mit einem anderen Objekt in einem ähnlichen Stil wie Facebook / Instagram / Chrome / etc anzupassen. Apps.

In meiner .xib-Datei habe ich meine Komponenten in eine Freiformansicht geladen: http://imgur.com/0z9yebJ (Entschuldigung, ich habe nicht den Repräsentanten, um Bilder zu inline)

Beachten Sie, dass die Tabelle in der linken Seitenleiste hinter der Hauptkopfansicht angeordnet ist. Sie können es dem Screenshot nicht entnehmen, aber es hat auch die gleiche y-Position wie die Hauptkopfansicht. Da die Eigenschaft contentInset in der UITableView nicht sichtbar ist, wird sie auf 76 festgelegt (die Höhe der Hauptheaderansicht).

Damit die Hauptheaderansicht im Einklang mit UIScrollView nach oben verschoben wird, verwende ich die scrollViewDidScroll-Methoden von UIScrollViewDelegate, um einige Berechnungen durchzuführen und das contentInset von UIScrollView sowie den Frame der Hauptheaderansicht zu ändern.

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

Die erste if- Anweisung erledigt den größten Teil des schweren Hebens, aber ich musste die beiden anderen einbeziehen, um Situationen zu bewältigen, in denen der Benutzer kräftig zieht und die an scrollViewDidScroll gesendeten anfänglichen contentOffset-Werte außerhalb des Bereichs des ersten if liegen Anweisung liegen.

Letztendlich funktioniert das wirklich gut für mich. Ich hasse es, meine Projekte mit einer Reihe aufgeblähter Unterklassen zu beladen. Ich kann nicht darüber sprechen, ob dies in Bezug auf die Leistung die beste Lösung ist (ich habe immer gezögert, Code in scrollViewDidScroll einzufügen, da er ständig aufgerufen wird), aber der Code-Footprint ist der kleinste, den ich je gesehen habe Lösung für dieses Problem, ohne dass eine UITableView in eine UIScrollView verschachtelt werden muss (Apple rät in der Dokumentation davon ab, und Touch-Ereignisse werden in der UITableView etwas unkonventionell). Hoffe das hilft jemandem!



2

Ich habe versucht, GTScrollNavigationBar zu implementieren, aber für meine App musste ich die Einschränkungen für das automatische Layout ändern. Ich habe beschlossen, ein Beispiel für meine Implementierung auf GitHub zu veröffentlichen, falls jemand anderes dies mit automatischem Layout tun muss. Das andere Problem, das ich bei den meisten anderen Implementierungen hatte, ist, dass die Leute die Grenzen der Bildlaufansicht nicht festlegen, um den Parallaxen-Bildlaufeffekt zu vermeiden, den Sie beim Bildlauf erzeugen und gleichzeitig die Größe der Bildlaufansicht anpassen.

Schauen Sie sich JSCollapsingNavBarViewController an, wenn Sie dies mit automatischem Layout tun müssen. Ich habe zwei Versionen hinzugefügt, eine nur mit der Navigationsleiste und eine mit einer Unterleiste unterhalb der Navigationsleiste, die vor dem Reduzieren der Navigationsleiste zusammenfällt.


1

Ich habe es auf diese Weise versucht, ich hoffe, es wird helfen. Implementieren Sie einfach den Code in der Delegate-Methode und stellen Sie die gewünschte Ansicht / Unteransicht ein

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

1

Eine Erweiterung von @Iwburks Antwort ... Anstatt den Ursprung der Navigationsleiste zu ändern, musste ich die Größe der Navigationsleiste erweitern / verkleinern.

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

Es funktioniert noch nicht mit der stoppedScrollingMethode, ich werde ein Update posten, wenn ich es habe


0

All diese Ansätze scheinen zu kompliziert ... Also habe ich natürlich meine eigenen gebaut:

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
    }

}

navigationBar.height? scrollView.height? Verwenden Sie eine Erweiterung auf dem frameGrundstück?
Ely

0

Ich habe alle Antworten in Ziel-C gefunden. Dies ist meine Antwort in Swift 3. Dies ist ein sehr allgemeiner Code, der direkt verwendet werden kann. Es funktioniert sowohl mit UIScrollView als auch mit 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
    }
}

Die Logik zum Setzen von Alpha auf Navigationselemente wird aus der Antwort von @ WayneBurkett kopiert und in Swift 3 neu geschrieben.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.