Имитировать Facebook скрытие / отображение расширения / сжатия панели навигации

128

В новом приложении iOS7 Facebook для iPhone, когда пользователь прокручивает страницу вверх, navigationBarпостепенно скрывается до точки, где оно полностью исчезает. Затем, когда пользователь прокручивает вниз, navigationBarпостепенно появляется.

Как бы вы сами реализовали такое поведение? Мне известно о следующем решении, но оно сразу исчезает и вообще не привязано к скорости жеста прокрутки пользователя.

[navigationController setNavigationBarHidden: YES animated:YES];

Я надеюсь, что это не дубликат, поскольку я не уверен, как лучше всего описать поведение «расширения / сжатия».

Эль Мокосо
источник
2
Те же вопросы: stackoverflow.com/questions/21929220/... Обратите внимание , что невероятно трудно , чтобы абсолютно соответствовать поведению Safari. Там есть очень и очень сложные правила!
Fattie
1
В своем проекте я использовал этот проект, и он работал нормально. Взгляните на его документацию.
Vinicius
github.com/bryankeller/BLKF flexibleHeightBar позволит вам делать то, что вы хотите, и многое другое. Он позволяет точно указать, как полоса будет выглядеть на каждом этапе перехода от развернутого к минимизированному. Он даже позволяет вам определять свое собственное поведение, поэтому он может действовать как Safari, Facebook или какое-либо другое приложение.
blkhp19
Я не использовал uinavigationbar, но вместо этого добавил uiview. Представление, воспроизводящее панель навигации, будет расширяться и сжиматься в зависимости от прокрутки. Я использовал метод делегата scrollViewDidScroll для выполнения задачи. Вы можете проверить и выполнить приведенный ниже исходный код .. dropbox.com/s/b2c0zw6yvchaia5/FailedBanks.zip?dl=0
Дипак Такур

Ответы:

162

Решение, данное @peerless, - отличное начало, но оно запускает анимацию только при начале перетаскивания, без учета скорости прокрутки. Это приводит к более нестабильной работе, чем в приложении Facebook. Чтобы соответствовать поведению Facebook, нам необходимо:

  • скрыть / показать панель навигации со скоростью, пропорциональной скорости перетаскивания
  • запускать анимацию, чтобы полностью скрыть панель, если прокрутка останавливается, когда панель частично скрыта
  • исчезают элементы навигационной панели по мере того, как панель сокращается.

Во-первых, вам понадобится следующее свойство:

@property (nonatomic) CGFloat previousScrollViewYOffset;

А вот 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];
    }
}

Вам также понадобятся эти вспомогательные методы:

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

Для немного другого поведения замените строку, которая меняет положение полосы при прокрутке ( elseблок внутри scrollViewDidScroll), на эту:

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

При этом полоса позиционируется на основе последнего процента прокрутки, а не абсолютного значения, что приводит к более медленному затуханию. Исходное поведение больше похоже на Facebook, но мне тоже нравится это.

Примечание: это решение только для iOS 7+. Обязательно добавьте необходимые проверки, если вы поддерживаете более старые версии iOS.

Wayne
источник
Это работает. За исключением того, что вы предполагаете, что элементы кнопок панели имеют настраиваемые представления. В моем случае у меня нет специального представления. Таким образом, приведенное выше не скрывает кнопки панели. Я думаю , что @peerless решение лучше для скрытия и отображения NavBar элементов
Дануш
1
Ты прав. Я могу поискать более общее решение в эти выходные. Не должно быть слишком сложно настроить таргетинг на элементы по умолчанию вместо customView.
Уэйн
Я не рассматривал проблему @Dhanush, но у меня есть обновление.
Уэйн
1
Я исправил проблему с кнопками стандартной панели, но в этом коде есть другая проблема. Если ScrollView«s contentSizeменьше , чем кадр, анимация скольжения не работает. Также убедитесь, что вы сбросили альфа всех элементов навигации обратно на 1,0 дюйма viewDidDisappear.
Legoless
1
Вы проверяли это решение на контроллерах, которые используют AutoLayout? В моем случае без AutoLayout все работает нормально, но когда он включается, под ним все равно видна какая-то странная белая полоса
Александр Б.
52

РЕДАКТИРОВАТЬ: только для iOS 8 и выше.

Вы можете попробовать использовать

self.navigationController.hidesBarsOnSwipe = YES;

Работает для меня.

Если ваше кодирование выполняется быстро, вы должны использовать этот способ (из qaru.site/questions/235 / ... )

navigationController?.hidesBarsOnSwipe = true
Педро Ромао
источник
это недоступно в iOS <8.0
Петар,
1
Как сказал pet60t0, и я прошу прощения, работает только для iOS 8 и выше.
Педро Ромао,
4
Как вернуть его после этого?
C0D3,
поведение немного странное. Я особо не исследовал, но если прокрутить быстрее, он снова появится.
Педро Ромао
@ PedroRomão отличный ответ.
Gagan_iOS
43

Еще одна реализация: выпущен TLYShyNavBar v1.0.0!

Я решил сделать свои собственные после того, как попробовал предоставленные решения, и для меня они либо работали плохо, имели высокий барьер входа и код стандартной пластины, либо не имели представления расширения под панелью навигации. Чтобы использовать этот компонент, все, что вам нужно сделать, это:

self.shyNavBarManager.scrollView = self.scrollView;

Да, и это испытано в боевых условиях в нашем собственном приложении.

Mazyod
источник
@TimArnold Спасибо за отзыв! Я исправил эту проблему, просто еще не обновил модуль> _ <сделает это прямо сейчас! .. Под обновлен!
Mazyod 06
Я сделаю еще один шанс! Спасибо!
Тим Кембер
Я ценю вашу помощь. Похоже, ваша фиксация помогла. У меня все еще возникает странная проблема, когда мой UICollectionView не изменяется должным образом, как панель навигации, и поэтому ячейки, поднимающиеся вверх там, где ИСПОЛЬЗУЕТСЯ панель навигации, обрезаются, поскольку они находятся за пределами границ представления коллекции. Вы знаете, почему это может происходить?
Tim Camber
4
Цифры: нашел свое решение через несколько секунд после написания этого комментария. Я должен был убедиться , что extendedLayoutIncludesOpaqueBarsбыл установлен YESна моемUICollectionViewController
Тим Камбере
@TimArnold Это круто! У другого парня была такая же проблема , и, надеюсь, ваше решение поможет ему.
Mazyod 09
33

Вы можете взглянуть на мою GTScrollNavigationBar . Я создал подкласс UINavigationBar, чтобы он прокручивался на основе прокрутки UIScrollView.

Примечание. Если у вас НЕПРЕРЫВНАЯ панель навигации, прокрутка должна РАСШИРЯТЬСЯ, поскольку панель навигации СКРЫВАЕТСЯ. Это именно то, что делает GTScrollNavigationBar. (Так же, как, например, в Safari на iOS.)

Туи
источник
Кстати, для всех, кто читает, как именно вызвать initWithNavigationBarClass ... stackoverflow.com/questions/22286166
Fattie
@Thuy отличная работа, чувак! Итак, у меня это отлично работает на контроллере табличного представления, за исключением одного ... У меня есть потянуть, чтобы обновить, реализованное наверху. Это становится странно при попытке развернуть и обновить. Есть ли обходной путь для этого?
Aherrick
@Thuy также ... допустим, у меня был контроллер представления, в котором я реализовал табличное представление внизу. Я хочу подключиться к этому табличному представлению, которое работает, однако у меня есть другое представление, которое находится над табличным представлением, которое я тоже хочу исчезнуть. Как это могло работать?
Aherrick
25

iOS8 включает в себя свойства, позволяющие бесплатно скрывать панель навигации. Есть видео WWDC, которое демонстрирует это, ищите «Просмотр улучшений контроллера в iOS 8».

Пример :

class QuotesTableViewController: UITableViewController {

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

    navigationController?.hidesBarsOnSwipe = true
}

}

Прочие свойства:

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

Найдено через http://natashatherobot.com/navigation-bar-interactions-ios8/

Майкл Петерсон
источник
12

У меня есть какое-то быстрое и грязное решение для этого. Не проводил углубленного тестирования, но вот идея:

Это свойство сохранит все элементы на панели навигации для моего класса UITableViewController.

@property (strong, nonatomic) NSArray *navBarItems;

В том же классе 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];
}

Это только для ios> = 7, я знаю, это ужасно, но это быстрый способ добиться этого. Любые комментарии / предложения приветствуются :)

несравненный
источник
12

Это работает для 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];

И если вы хотите, чтобы при нажатии на строку состояния отображалась панель навигации, вы можете сделать это:

- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
     self.navigationController.navigationBarHidden = NO;
}
Чжун Хуэйвэнь
источник
10

Вот моя реализация: SherginScrollableNavigationBar .

В моем подходе я использую KVOдля наблюдения UIScrollViewза состоянием, поэтому нет необходимости использовать делегат (и вы можете использовать этот делегат для всего, что вам нужно).

Валентин Шергин
источник
К вашему сведению, это не всегда работает правильно. Я тоже пробовал это, и это работает, пока вы не "подпрыгиваете" при прокрутке. Похоже, KVO не срабатывает при отскоке. Тем не менее, вызов делегата для contentOffset запускается.
Joris Mans,
7

Пожалуйста, попробуйте это мое решение и дайте мне знать, почему оно не так хорошо, как предыдущие ответы.

- (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
источник
1
Я сделал что-то подобное, и это лучшее решение. Я пробовал несколько хаков и библиотек, и это единственная, которая работает для iOS 9, когда tableView не покрывает весь экран. Престижность!
skensell,
6

Один из способов, которым я добился этого, заключается в следующем.

Зарегистрируйте свой контроллер представления, например, как UIScrollViewDelegateваш UITableView.

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

Из UIScrollViewDelegateметодов вы можете получить новый contentOffset и соответственно перевести его UINavigationBarвверх или вниз.

Установка альфа-канала подвидов также может быть выполнена на основе некоторых пороговых значений и факторов, которые вы можете установить и вычислить.

Надеюсь, поможет!

Диана Суле
источник
Нашла похожий пост здесь
Дайана Суле
Спасибо, Диана! Я подозревал, что мне, возможно, придется реализовать методы UIScrollViewDelegate, но казалось, что это может быть немного излишним. Как только я разберусь с этим, я выложу его. Ура!
Эль Мокосо
4

В дополнение к ответу Ивбурка я добавил следующее, чтобы исправить альфа-проблему на ненастроенных панелях навигации и сбросить панель навигации в методе 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];
}
синий лед
источник
Есть ли другой способ, кроме как перебирать подпредставления?
thisiscrazy4
Из того, что я мог сказать на не настраиваемой панели навигации, всего 4 подпредставления: _UINavigationBarBackground, UINavigationItemView, UINavigationItemButtonView и _UINavigationBarBackIndicatorView. Цикл выполняется довольно быстро и, похоже, не влияет на производительность моего приложения.
blueice
Поскольку _UINavigationBarBackground всегда является первым вложенным представлением, вы можете просто получить прямой доступ к остальным: ((UIView *) self.navigationController.navigationBar.subviews [1]). Alpha = alpha;
blueice
4

Я искал решение, допускающее любой стиль и любое поведение. Вы заметите, что во многих приложениях конденсация полосок различается. И, конечно же, панель в разных приложениях выглядит по-разному.

Я создал решение этой проблемы с https://github.com/bryankeller/BLKF flexibleHeightBar/

Вы можете определить свои собственные правила поведения, чтобы контролировать, как и когда полоса сжимается и растет, и вы можете точно определить, как вы хотите, чтобы подвиды полоски реагировали на уплотнение или рост полоски.

Взгляните на мой проект, если вам нужна большая гибкость для создания любой панели заголовка, которую вы можете придумать.

blkhp19
источник
Как я могу добавить кнопку в этот customHeaderView, которая будет скрываться при прокрутке вверх. Мне не нужна статическая кнопка. Возможно ли? Я попытался создать одну кнопку в качестве подпредставления, но она не получает никаких касаний.
abhimuralidharan
3

Я пытался подражать этому поведению в ситуации, когда мне требовался настраиваемый заголовок, расположенный около UITableView. Я свернул свою собственную «навигационную» панель, потому что она находится под множеством других вещей на странице, и я хотел, чтобы заголовки разделов соответствовали поведению «стыковки» по умолчанию. Я думаю, что нашел довольно умный и лаконичный способ настроить UITableView / UIScrollView вместе с другим объектом в стиле, аналогичном тому, который мы видели в Facebook / Instagram / Chrome / и т. Д. Программы.

В моем файле .xib мои компоненты загружены в произвольном виде: http://imgur.com/0z9yebJ (извините, у меня нет репутации для встроенных изображений)

Обратите внимание, что на левой боковой панели таблица расположена за основным видом заголовка. Вы не можете сказать по снимку экрана, но он также имеет ту же позицию по оси Y, что и основной вид заголовка. Поскольку он простирается вне поля зрения, свойство contentInset в UITableView установлено на 76 (высота основного представления заголовка).

Чтобы заставить представление основного заголовка скользить вверх вместе с UIScrollView, я использую методы scrollViewDidScroll UIScrollViewDelegate для выполнения некоторых вычислений и изменения contentInset UIScrollView, а также фрейма основного представления заголовка.

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

Первый оператор if выполняет большую часть тяжелой работы, но мне пришлось включить два других для обработки ситуаций, когда пользователь принудительно перетаскивает, а начальные значения contentOffset, отправленные в scrollViewDidScroll, выходят за пределы диапазона первого оператора if .

В конечном счете, у меня это действительно хорошо работает. Я ненавижу загружать свои проекты кучей раздутых подклассов. Я не могу сказать, является ли это лучшим решением с точки зрения производительности (я всегда не решался помещать какой-либо код в scrollViewDidScroll, поскольку он вызывается все время), но размер кода - самый маленький, который я видел в любом решение этой проблемы, и оно не связано с вложением UITableView в UIScrollView (Apple не советует этого в документации, а события касания в UITableView выглядят немного странно). Надеюсь, это кому-то поможет!

Брайан
источник
3

HidingNavigationBar - отличный проект, который,если хотите, скрывает панель навигации и панель вкладок .

HidingNavigationBar поддерживает скрытие / отображение следующих элементов представления:

UINavigationBar

UINavigationBar и расширение UIView

UINavigationBar и UIToolbar

UINavigationBar и UITabBar

https://github.com/tristanhimmelman/HidingNavigationBar

Майкл Петерсон
источник
2

Я попытался реализовать GTScrollNavigationBar, но мое приложение потребовало от меня изменения ограничений автоматического макета. Я решил разместить пример своей реализации на GitHub на тот случай, если кому-то еще придется делать это с помощью автоматического макета. Другая проблема, с которой я столкнулся с большинством других реализаций, заключается в том, что люди не устанавливают границы представления прокрутки, чтобы избежать эффекта прокрутки параллакса, который вы создаете при прокрутке и одновременной настройке размера прокрутки.

Ознакомьтесь с JSCollapsingNavBarViewController, если вам нужно сделать это с помощью автоматического макета. Я включил две версии, одну только с навигационной панелью, а другую с дополнительной панелью под навигационной панелью, которая сворачивается перед тем, как свернуть навигационную панель.

jwswart
источник
1

Я пробовал вот так, надеюсь, поможет. просто реализуйте код в методе делегата и установите желаемое представление / подпредставление

-(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
источник
1

Расширение ответа @Iwburk ... Вместо того, чтобы изменять происхождение панели навигации, мне нужно было увеличить / уменьшить размер панели навигации.

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

Он еще не работает с stoppedScrollingметодом, я опубликую обновление, когда оно у меня есть

jsetting32
источник
0

Все эти подходы кажутся слишком сложными ... Естественно, я построил свой собственный:

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
источник
navigationBar.height? scrollView.height? Вы используете пристройку к frameсобственности?
Эли,
0

Я нашел все ответы, данные в Objective-C. Это мой ответ в Swift 3. Это очень общий код, который можно использовать напрямую. Он работает как с UIScrollView, так и с 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
    }
}

Логика установки альфы для элементов навигации скопирована из ответа @ WayneBurkett и переписана в Swift 3.

Dev
источник
0

для Swift 4,5 - iOS 11 и выше

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

Расширение UIView

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

Вы должны navigationItem.titleViewприменить наборalpha

Жанг
источник