W nowej aplikacji iOS7 Facebook na iPhone'a, gdy użytkownik przewija w górę, navigationBar
stopniowo chowa się do punktu, w którym całkowicie znika. Następnie, gdy użytkownik przewija w dół, navigationBar
stopniowo 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ę”.
ios
iphone
objective-c
ios7
uinavigationbar
El Mocoso
źródło
źródło
Odpowiedzi:
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:
Najpierw będziesz potrzebować następującej właściwości:
@property (nonatomic) CGFloat previousScrollViewYOffset;
A oto
UIScrollViewDelegate
metody:- (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 (
else
blok 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.
źródło
customView
.ScrollView
jestcontentSize
mniejszy niż ramka, animacja przesuwnych nie działa. Upewnij się również, że zresetowałeś alfa wszystkich elementów nawigacyjnych z powrotem do 1,0 calaviewDidDisappear
.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
źródło
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.
źródło
extendedLayoutIncludesOpaqueBars
ustawionyYES
na moimUICollectionViewController
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).
źródło
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/
źródło
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 :)
źródło
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; }
źródło
Oto moja implementacja: SherginScrollableNavigationBar .
W moim podejściu używam
KVO
do obserwacjiUIScrollView
stanu, więc nie ma potrzeby korzystania z delegata (i możesz go używać do wszystkiego, czego potrzebujesz).źródło
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]; }
źródło
Oto jeden ze sposobów, w jaki to osiągnąłem.
Zarejestruj swój kontroler widoku, aby być
UIScrollViewDelegate
swoimUITableView
na przykład.- (void)scrollViewDidScroll:(UIScrollView *)scrollView; - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
Z poziomu
UIScrollViewDelegate
metod możesz pobrać nową zawartość i odpowiednio przetłumaczyćUINavigationBar
ją 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!
źródło
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]; }
źródło
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ć.
źródło
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!
źródło
HidingNavigationBar świetny projekt, który ukrywa pasek nawigacji i pasek kart, jeśli chcesz.
https://github.com/tristanhimmelman/HidingNavigationBar
źródło
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.
źródło
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 } }
źródło
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ą
stoppedScrolling
metodą, opublikuję aktualizację, gdy ją mamźródło
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 } }
źródło
frame
posesji?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.
źródło
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.titleView
ustawić niestandardowy zestawalpha
źródło