ios 模仿Facebook隐藏/显示扩展/收缩导航栏

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19819165/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:47:25  来源:igfitidea点击:

Imitate Facebook hide/show expanding/contracting Navigation Bar

iosiphoneobjective-cios7uinavigationbar

提问by El Mocoso

In the new iOS7 Facebook iPhone app, when the user scrolls up the navigationBargradually hides itself to a point where it completely vanishes. Then when the user scrolls down the navigationBargradually shows itself.

在新的 iOS7 Facebook iPhone 应用程序中,当用户向上滚动时navigationBar,它会逐渐隐藏到完全消失的地步。然后当用户向下滚动时,navigationBar逐渐显示自己。

How would you implement this behavior yourself? I am aware of the following solution but it disappears right away and it isn't tied to the speed of the user's scroll gesture at all.

您将如何自己实现这种行为?我知道以下解决方案,但它会立即消失,并且完全与用户滚动手势的速度无关。

[navigationController setNavigationBarHidden: YES animated:YES];

I hope this isn't a duplicate as I'm not sure how best to describe the "expanding/contracting" behavior.

我希望这不是重复的,因为我不确定如何最好地描述“扩展/收缩”行为。

回答by Wayne

The solution given by @peerless is a great start, but it only kicks off an animation whenever dragging begins, without considering the speed of the scroll. This results in a choppier experience than you get in the Facebook app. To match Facebook's behavior, we need to:

@peerless 给出的解决方案是一个很好的开始,但它只在拖动开始时启动动画,而没有考虑滚动速度。这会带来比您在 Facebook 应用程序中获得的更不稳定的体验。为了匹配 Facebook 的行为,我们需要:

  • hide/show the navbar at a rate that is proportional to the rate of the drag
  • kick off an animation to completely hide the bar if scrolling stops when the bar is partially hidden
  • fade the navbar's items as the bar shrinks.
  • 以与拖动速率成正比的速率隐藏/显示导航栏
  • 如果在栏部分隐藏时滚动停止,则启动动画以完全隐藏栏
  • 随着栏缩小,使导航栏的项目淡化。

First, you'll need the following property:

首先,您需要以下属性:

@property (nonatomic) CGFloat previousScrollViewYOffset;

And here are the UIScrollViewDelegatemethods:

以下是UIScrollViewDelegate方法:

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

You'll also need these helper methods:

您还需要这些辅助方法:

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

For a slightly different behavior, replace the line that re-positions the bar when scrolling (the elseblock in scrollViewDidScroll) with this one:

对于稍微不同的行为,将滚动时重新定位栏的行(中的elsescrollViewDidScroll)替换为以下行:

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

This positions the bar based on the last scroll percentage, instead of an absolute amount, which results in a slower fade. The original behavior is more Facebook-like, but I like this one, too.

这将根据上次滚动百分比而不是绝对数量来定位条,这会导致淡入淡出较慢。最初的行为更像 Facebook,但我也喜欢这个。

Note: This solution is iOS 7+ only. Be sure to add the necessary checks if you're supporting older versions of iOS.

注意:此解决方案仅适用于 iOS 7+。如果您支持旧版本的 iOS,请务必添加必要的检查。

回答by Pedro Rom?o

EDIT: Only for iOS 8 and above.

编辑:仅适用于 iOS 8 及更高版本。

You can try use

您可以尝试使用

self.navigationController.hidesBarsOnSwipe = YES;

Works for me.

为我工作。

If your coding in swift you have to use this way (from https://stackoverflow.com/a/27662702/2283308)

如果您在 swift 中编码,则必须使用这种方式(来自https://stackoverflow.com/a/27662702/2283308

navigationController?.hidesBarsOnSwipe = true

回答by Mazyod

Here is one more implementation: TLYShyNavBarv1.0.0 released!

这是另一个实现:TLYShyNavBarv1.0.0 发布!

I decided to make my own after trying the solutions provided, and to me, they were either performing poorly, had a a high barrier of entry and boiler plate code, or lacked the extension view beneath the navbar. To use this component, all you have to do is:

在尝试了所提供的解决方案后,我决定自己制作,对我来说,它们要么表现不佳,要么具有很高的进入门槛和样板代码,要么在导航栏下方缺少扩展视图。要使用这个组件,你所要做的就是:

self.shyNavBarManager.scrollView = self.scrollView;

Oh, and it is battle tested in our own app.

哦,它在我们自己的应用程序中经过了实战测试。

回答by Thuy

You can have a look at my GTScrollNavigationBar. I have subclassed UINavigationBar to make it scroll based on the scrolling of a UIScrollView.

你可以看看我的GTScrollNavigationBar。我已经将 UINavigationBar 子类化以使其基于 UIScrollView 的滚动而滚动。

Note: If you have an OPAQUE navigation bar, the scrollview must EXPAND as the navigation bar gets HIDDEN. This is exactly what GTScrollNavigationBar does. (Just as in for example Safari on iOS.)

注意:如果您有一个不透明的导航栏,滚动视图必须在导航栏隐藏时展开。这正是 GTScrollNavigationBar 所做的。(就像在 iOS 上的 Safari 中一样。)

回答by Michael Peterson

iOS8 includes properties to get the navigation bar hiding for free. There is a WWDC video that demonstrates it, search for "View Controller Advancements in iOS 8".

iOS8 包含可以免费隐藏导航栏的属性。有一个演示它的 WWDC 视频,搜索“iOS 8 中的视图控制器改进”。

Example:

示例

class QuotesTableViewController: UITableViewController {

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

    navigationController?.hidesBarsOnSwipe = true
}

}

}

Other properties:

其他属性:

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

Found via http://natashatherobot.com/navigation-bar-interactions-ios8/

通过http://natashatherobot.com/navigation-bar-interactions-ios8/找到

回答by Zhong Huiwen

This works for iOS 8 and above and ensures that the status bar still retains its background

这适用于 iOS 8 及更高版本,并确保状态栏仍然保留其背景

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

And if you want to show the nav bar when you tap on the status bar you can do this:

如果你想在点击状态栏时显示导航栏,你可以这样做:

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

回答by peerless

I have some kind of a quick and dirty solution for that. Haven't made any in-depth testing but here's the idea:

我有一些快速而肮脏的解决方案。还没有进行任何深入的测试,但这是一个想法:

That property will keep all the items in the navbar for my UITableViewController class

该属性将为我的 UITableViewController 类保留导航栏中的所有项目

@property (strong, nonatomic) NSArray *navBarItems;

In the same UITableViewController class I have:

在同一个 UITableViewController 类中,我有:

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

That's only for ios >= 7, it's ugly I know but a quick way to achieve this. Any comments/suggestions are welcome :)

这仅适用于 ios >= 7,我知道这很丑陋,但这是实现这一目标的快速方法。欢迎任何意见/建议:)

回答by Valentin Shergin

Here is my implementation: SherginScrollableNavigationBar.

这是我的实现:SherginScrollableNavigationBar

In my approach I am using KVOfor observing UIScrollView's state, so there is no necessity to use a delegate (and you can use this delegate for whatever else you need).

在我的方法中,我KVO用于观察UIScrollView的状态,因此没有必要使用委托(并且您可以将此委托用于任何其他需要)。

回答by Nishant

Please try this solution of mine and let me know why this ain't as good as the previous answers.

请尝试我的这个解决方案,让我知道为什么这不如以前的答案。

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

回答by Diana Sule

One way that I've accomplished this is the following.

我完成此操作的一种方法如下。

Register your view controller to be the UIScrollViewDelegateof your UITableViewfor example.

将您的视图控制器注册UIScrollViewDelegate为您UITableView的例如。

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

From within de UIScrollViewDelegatemethods you can get the new contentOffset and translate your UINavigationBarup or down accordingly.

从 deUIScrollViewDelegate方法中,您可以获得新的 contentOffset 并UINavigationBar相应地向上或向下平移。

Setting the alpha of the subviews can also be done based on some threshold values and factors you can set and compute.

也可以根据您可以设置和计算的一些阈值和因素来设置子视图的 alpha。

Hope it helps!

希望能帮助到你!