preferStatusBarStyle nie jest wywoływany


257

Śledziłem ten wątek, aby go zastąpić -preferredStatusBarStyle, ale nie jest on wywoływany. Czy są jakieś opcje, które mogę zmienić, aby to włączyć? (Używam XIB w moim projekcie).


W jakim kontekście nie jest wywoływany: symulator? na urządzeniu?
bneely

@Beely oba z nich.
trgoofi

Używasz symulatora systemu iOS 7, urządzenia z systemem iOS 7, a Twój podstawowy zestaw SDK to 7.0?
bneely

@Bneely iOS SDK 7.0 jest wyświetlany poniżej nazwy mojego projektu, czy to oznacza, że ​​mój podstawowy SDK to 7.0?
trgoofi

W ustawieniach kompilacji wartość „Podstawowy zestaw SDK” jest ustawiona. Wygląda na to, że Twój projekt jest ustawiony na 7.0.
bneely

Odpowiedzi:


117

Możliwa przyczyna pierwotna

Miałem ten sam problem i zorientowałem się, że tak się dzieje, ponieważ nie ustawiałem kontrolera widoku głównego w oknie aplikacji.

W UIViewControllerktórym wdrożyłem preferredStatusBarStylebył używany w UITabBarController, który kontrolował wygląd widoków na ekranie.

Kiedy ustawiłem kontroler widoku głównego, aby wskazywał na to UITabBarController, zmiany paska stanu zaczęły działać poprawnie, zgodnie z oczekiwaniami (i preferredStatusBarStylewywołano metodę).

(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ... // other view controller loading/setup code

    self.window.rootViewController = rootTabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

Metoda alternatywna (przestarzałe w iOS 9)

Alternatywnie możesz wywołać jedną z następujących metod, odpowiednio, w każdym z kontrolerów widoku, w zależności od koloru tła, zamiast konieczności używania setNeedsStatusBarAppearanceUpdate:

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

lub

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];

Pamiętaj, że musisz również ustawić UIViewControllerBasedStatusBarAppearancena NOplik plist, jeśli używasz tej metody.


2
Mam taki sam problem jak Ty, nie ustawiając kontrolera widoku root. Jak do diabła to znalazłeś?
trgoofi

1
Podejrzewałem, że coś w ramce nie otrzymywało powiadomienia setNeedsStatusBarAppearanceUpdate- moje podejrzenia zostały potwierdzone, kiedy dokonałem tej zmiany.
AbdullahC

2
Pokrewnym problemem, który znalazłem w aplikacji, był kontroler widoku z pełnoekranowym kontrolerem widoku potomnego, który nie przesłaniał childViewControllerForStatusBarStyle i childViewControllerForStatusBarHidden, aby zwrócić ten kontroler widoku potomnego. Jeśli masz własną hierarchię kontrolera widoku, musisz podać te metody, aby poinformować system, którego kontrolera widoku należy użyć do określenia stylu paska stanu.
Jon Steinmetz

ustawienie rootviewcontroller niczego nie zmienia. Powinieneś pracować z komentarzem Jona. I zachowaj ostrożność podczas wywoływania setneedsstatusbarappearanceUpdate. Powinieneś zadzwonić do rodzica do pracy.
doozMen

1
@Hippo jesteś geniuszem !! Jak doszło do tego, że stało się tak z powodu braku ustawienia rootviewcontroller?
ViruMax,

1019

Dla wszystkich korzystających z UINavigationController:

UINavigationControllerNie do przodu na preferredStatusBarStylerozmowy do jej zdaniem dziecko kontrolerów. Zamiast tego zarządza swoim własnym stanem - tak jak powinien, rysuje u góry ekranu, na którym znajduje się pasek stanu, i dlatego powinien być za to odpowiedzialny. W związku z tym implementacja preferredStatusBarStylew VC w kontrolerze nawigacyjnym nic nie zrobi - nigdy nie zostanie wywołana.

Sztuką jest to, czego UINavigationControllerużywa, aby zdecydować, po co zwrócić UIStatusBarStyleDefaultlub UIStatusBarStyleLightContent. Opiera to na swoim UINavigationBar.barStyle. Ustawienie domyślne ( UIBarStyleDefault) powoduje wyświetlenie ciemnego UIStatusBarStyleDefaultpaska stanu pierwszego planu . I UIBarStyleBlackda UIStatusBarStyleLightContentpasek stanu.

TL; DR:

Jeśli chcesz UIStatusBarStyleLightContentw UINavigationControllerużyciu:

self.navigationController.navigationBar.barStyle = UIBarStyleBlack;

59
Miły! Pamiętaj, że preferredStatusBarStylew rzeczywistości zostanie wywołany na kontrolerze widoku potomnego, jeśli ukryjesz pasek nawigacji (ustawiony navigationBarHiddenna YES), dokładnie tak, jak to właściwe.
Patrick Pijnappel,

25
Dziękuję za tę odpowiedź. Jeśli chcesz ustawić barStyle dla wszystkich pasków nawigacji, zadzwoń[[UINavigationBar appearance] setBarStyle:UIBarStyleBlack]
Thomas Desert

15
Idealna odpowiedź. Żadna z pozostałych odpowiedzi w SO nie uwzględniała UINavigationController. 2 godziny walenia głową w klawiaturę.
Ryan Alford

10
Wyrazy uznania dla @Patrick za wskazanie, że navigationBarHiddenzestaw do YESrzeczywiście preferredStatusBarStylezadzwonił, oraz ostrzeżenie dla tych, którzy mogą się na to natknąć: działa z navigationBarHidden, ale nie z navigationBar.hidden!
jcaron

4
powinno być oczywiste, ale aby to działało, musisz także ustawić „Wyświetl wygląd paska stanu na kontrolerze” ustawioną na TAK w Info.plist.
Code Baller

99

Więc faktycznie dodałem kategorię do UINavigationController, ale użyłem metod:

-(UIViewController *)childViewControllerForStatusBarStyle;
-(UIViewController *)childViewControllerForStatusBarHidden;

i sprawiły, że zwracają bieżący widoczny UIViewController. Dzięki temu bieżący kontroler widoku widocznego może ustawić własny preferowany styl / widoczność.

Oto pełny fragment kodu:

W Swift:

extension UINavigationController {

    public override func childViewControllerForStatusBarHidden() -> UIViewController? {
        return self.topViewController
    }

    public override func childViewControllerForStatusBarStyle() -> UIViewController? {
        return self.topViewController
    }
}

W celu C:

@interface UINavigationController (StatusBarStyle)

@end

@implementation UINavigationController (StatusBarStyle)

-(UIViewController *)childViewControllerForStatusBarStyle {
    return self.topViewController;
}

-(UIViewController *)childViewControllerForStatusBarHidden {
    return self.topViewController;
}

@end

I na dokładkę, oto jak jest to zaimplementowane następnie w UIViewController:

W Swift

override public func preferredStatusBarStyle() -> UIStatusBarStyle {
    return .LightContent
}

override func prefersStatusBarHidden() -> Bool {
    return false
}

W celu C

-(UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent; // your own style
}

- (BOOL)prefersStatusBarHidden {
    return NO; // your own visibility code
}

Na koniec upewnij się, że lista aplikacji NIE ma ustawienia „Wyświetl pasek stanu oparty na kontrolerze” ustawionego na NIE. Usuń tę linię lub ustaw ją na TAK (która, jak sądzę, jest teraz domyślna dla iOS 7?)


Wygląda na to, że return self.topViewController;działa dla mnie, ale return self.visibleViewController;- nie
k06a

visibleViewController może zwrócić aktualnie prezentowany kontroler modalny po jego odrzuceniu. Który jest bummer. Użyj topViewController.
Ben Sinclair,

1
@ d.lebedev ok, ale nie sądzę, żeby którykolwiek z tych problemów dotyczył tutaj. Nie musisz wywoływać supertej metody i naprawdę chcesz zmienić zachowanie wszystkich kontrolerów tego typu
ed '

1
to nie działa dla mnie w iOS 9.3. To chyba problem: ten problem ma szczególne znaczenie, ponieważ wiele klas kakao jest implementowanych za pomocą kategorii. Metoda zdefiniowana przez platformę, którą próbujesz zastąpić, mogła sama zostać zaimplementowana w kategorii, a więc która implementacja ma pierwszeństwo, nie jest zdefiniowana.
vikingosegundo

2
To źle i psuje się w iOS 13.4. Ponieważ rozszerzenie klas celów C w Swift jest realizowane za pomocą kategorii Celu C. Zastępowanie metod za pomocą kategorii celu C nie jest zalecane i prawdopodobnie się nie powiedzie. Zobacz stackoverflow.com/a/38274660/2438634
Marc Etcheverry

79

Dla każdego, kto wciąż zmaga się z tym, to proste rozszerzenie w trybie szybkim powinno rozwiązać problem.

extension UINavigationController {
    override open var childForStatusBarStyle: UIViewController? {
        return self.topViewController
    }
}

10
Zasługujesz na medal.
nikans

2
Dziękuję bardzo człowieku. Zamiast tego zwracałem widocznyViewViewController bez powodzenia.
Fábio Salata

1
To jest złoto. Mam kontroler nawigacji osadzony w pasku kart i właśnie wrzuciłem to do pliku, a teraz mogę zmienić wygląd paska stanu w dowolnym miejscu.
Vahid Amiri

2
To źle i psuje się w iOS 13.4. Ponieważ rozszerzenie klas celów C w Swift jest realizowane za pomocą kategorii Celu C. Zastępowanie metod za pomocą kategorii celu C nie jest zalecane i prawdopodobnie się nie powiedzie. Zobacz stackoverflow.com/a/38274660/2438634
Marc Etcheverry

1
@MarcEtcheverry ta konkretna instancja nie była zła. Faktem jest, że podklasy innych obiektów / protokołów, takich jak UINavigationController, nie miały wcześniejszej ich implementacji w celu konfliktu w dynamicznej wysyłce. W rzeczywistych podklasach nie było żadnych ustawień domyślnych ani implementacji, dlatego był to najczystszy sposób na wdrożenie tego w aplikacji bez tworzenia niepotrzebnej zależności (okresu). Niestety wydaje się, że 13.4 zmieniło to zachowanie. Domyślam się, że za kulisami mają teraz kontrolę lub implementację, której nie ma od lat .........
TheCodingArt

20

Moja aplikacja stosować wszystkie trzy: UINavigationController, UISplitViewController, UITabBarController, a więc wszystkie one wydają się przejąć kontrolę nad pasku stanu i spowoduje preferedStatusBarStyleaby nie nazwać ich dzieci. Aby zastąpić to zachowanie, możesz utworzyć rozszerzenie takie, jak wspomniano w pozostałych odpowiedziach. Oto rozszerzenie dla wszystkich trzech, w Swift 4. Szkoda, że ​​Apple nie wyjaśniło tego rodzaju rzeczy.

extension UINavigationController {
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return self.topViewController
    }

    open override var childViewControllerForStatusBarHidden: UIViewController? {
        return self.topViewController
    }
}

extension UITabBarController {
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return self.childViewControllers.first
    }

    open override var childViewControllerForStatusBarHidden: UIViewController? {
        return self.childViewControllers.first
    }
}

extension UISplitViewController {
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return self.childViewControllers.first
    }

    open override var childViewControllerForStatusBarHidden: UIViewController? {
        return self.childViewControllers.first
    }
}

Edycja: aktualizacja zmian interfejsu API Swift 4.2

extension UINavigationController {
    open override var childForStatusBarStyle: UIViewController? {
        return self.topViewController
    }

    open override var childForStatusBarHidden: UIViewController? {
        return self.topViewController
    }
}

extension UITabBarController {
    open override var childForStatusBarStyle: UIViewController? {
        return self.children.first
    }

    open override var childForStatusBarHidden: UIViewController? {
        return self.children.first
    }
}

extension UISplitViewController {
    open override var childForStatusBarStyle: UIViewController? {
        return self.children.first
    }

    open override var childForStatusBarHidden: UIViewController? {
        return self.children.first
    }
}

1
To jedyne działające rozwiązanie. Wszystkie odpowiedzi w SO wskazują na standardowe rozwiązanie, które nie będzie działać dla żadnej aplikacji z NavigationControllers. Dziękuję Ci!!!
Houman

Używanie rozszerzeń do zastępowania jest po prostu złe. To nie jest bezpieczne. Istnieje wiele prostszych rozwiązań. Zamiast tego użyj podklasy.
Sulthan

2
To źle i psuje się w iOS 13.4. Ponieważ rozszerzenie klas celów C w Swift jest realizowane za pomocą kategorii Celu C. Zastępowanie metod za pomocą kategorii celu C nie jest zalecane i prawdopodobnie się nie powiedzie. Zobacz stackoverflow.com/a/38274660/2438634
Marc Etcheverry

1
@MarcEtcheverry ta konkretna instancja nie była zła. Faktem jest, że podklasy innych obiektów / protokołów, takich jak UINavigationController, nie miały wcześniejszej ich implementacji w celu konfliktu w dynamicznej wysyłce. W rzeczywistych podklasach nie było żadnych ustawień domyślnych ani implementacji, dlatego był to najczystszy sposób na wdrożenie tego w aplikacji bez tworzenia niepotrzebnej zależności (okresu). Niestety wydaje się, że 13.4 zmieniło to zachowanie. Domyślam się, że za kulisami mają teraz kontrolę lub implementację, której nie ma od lat .........
TheCodingArt

15

Odpowiedź Tysona jest poprawna w przypadku zmiany koloru paska stanu na białyUINavigationController .

Jeśli ktoś chce osiągnąć ten sam wynik, wpisując kod, AppDelegateużyj poniższego kodu i zapisz go w środkuAppDelegate's didFinishLaunchingWithOptions metody.

I nie zapomnij ustawić UIViewControllerBasedStatusBarAppearancenaYES w pliku .plist, inaczej zmiana nie będzie odzwierciedlać.

Kod

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     // status bar appearance code
     [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];

     return YES;
}

14

Na UINavigationController preferredStatusBarStylenie jest wywoływany, ponieważ topViewControllerjest preferowany self. Aby więc zostać preferredStatusBarStylewywołanym na UINavigationController, musisz go zmienićchildViewControllerForStatusBarStyle .

Rekomendacje

Zastąp swój UINavigationController w swojej klasie:

class MyRootNavigationController: UINavigationController {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
    override var childViewControllerForStatusBarStyle: UIViewController? {
        return nil
    }
}

Niezalecana alternatywa

Aby to zrobić dla wszystkich UINavigationController, możesz przesłonić rozszerzenie (ostrzeżenie: wpływa na UIDocumentPickerViewController, UIImagePickerController itp.), Ale prawdopodobnie nie powinieneś tego robić zgodnie z dokumentacją Swift :

extension UINavigationController {
    open override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return nil
    }
}

11

Oprócz odpowiedzi serenn, jeśli prezentujesz kontrolerowi widoku modalPresentationStyle(na przykład .overCurrentContext), powinieneś także wywołać to na nowo przedstawionym kontrolerze widoku:

presentedViewController.modalPresentationCapturesStatusBarAppearance = true

Nie zapomnij również zastąpić preferredStatusBarStyleprzedstawionego kontrolera widoku.


9

Dodatek do odpowiedzi Hippo: jeśli używasz UINavigationController, prawdopodobnie lepiej jest dodać kategorię:

//  UINavigationController+StatusBarStyle.h:

@interface UINavigationController (StatusBarStyle)

@end



//  UINavigationController+StatusBarStyle.m:

@implementation UINavigationController (StatusBarStyle)

- (UIStatusBarStyle)preferredStatusBarStyle
{
    //also you may add any fancy condition-based code here
    return UIStatusBarStyleLightContent;
}

@end

To rozwiązanie jest prawdopodobnie lepsze niż przejście na przestarzałe zachowanie.


Nie rób tego, na razie działa, ale może złamać przyszłe zachowanie. Po prostu zmień styl paska nawigacyjnego - patrz moja odpowiedź stackoverflow.com/a/19513714/505457
Tyson

2
Powinieneś użyć podklasy, a nie kategorii.
shuiyouren

2Tyson: Dlaczego to złamie przyszłe zachowanie? preferStatusBarStyle: jest preferowaną metodą Apple'a do ustawiania stylu paska stanu.
Artem Abramov,

2shuiyouren: Dlaczego powinienem zwiększać złożoność poprzez podklasowanie, jeśli mogę po prostu użyć kategorii i umieścić ją w każdym miejscu, w którym chcę? W każdym razie jest to kwestia architektury, a nie implementacji.
Artem Abramov,

2
@ArtemAbramov Ponieważ UINavigationController już implementuje preferredStatusBarStylei wykonuje logikę specyficzną dla UINavigationController. Obecnie ta logika jest oparta, navigationBar.barStyleale widzę, że dodawane są dodatkowe kontrole (np. UISearchDisplayControllerPrzejście do ukrycia trybu paska nawigacyjnego). Przesłaniając domyślną logikę tracisz całą tę funkcjonalność i pozostawiasz się otwarty na irytujące momenty w przyszłości. Zobacz moją odpowiedź powyżej, aby dowiedzieć się, jak to zrobić, jednocześnie obsługując wbudowane zachowanie kontrolera nawigacyjnego.
Tyson

9

Swift 4.2 i wyżej

Jak wspomniano w wybranej odpowiedzi , główną przyczyną jest sprawdzenie obiektu kontrolera widoku głównego okna.

Możliwe przypadki twojej struktury przepływu

  • Niestandardowy obiekt UIViewController jest kontrolerem widoku głównego okna.

    Twój kontroler widoku głównego okna jest obiektem UIViewController i dodatkowo dodaje lub usuwa kontroler nawigacji lub tabController w oparciu o przepływ aplikacji.

    Ten rodzaj przepływu jest zwykle używany, jeśli aplikacja ma przepływ przed logowaniem na stosie nawigacji bez kart i przepływ po logowaniu za pomocą kart, a być może każda karta zawiera ponadto kontroler nawigacji.

  • Obiekt TabBarController jest kontrolerem widoku głównego okna

    Jest to przepływ, w którym kontrolerem widoku głównego okna jest tabBarController, prawdopodobnie każda karta zawiera ponadto kontroler nawigacji.

  • Obiekt NavigationController jest kontrolerem widoku głównego okna

    Jest to przepływ, w którym kontrolerem widoku głównego okna jest NavigationController.

    Nie jestem pewien, czy istnieje możliwość dodania kontrolera paska kart lub nowego kontrolera nawigacyjnego do istniejącego kontrolera nawigacyjnego. Ale jeśli jest taki przypadek, musimy przekazać kontrolę stylu paska stanu do następnego kontenera. Dodałem więc to samo sprawdzenie w rozszerzeniu UINavigationController, aby znaleźćchildForStatusBarStyle

Użyj następujących rozszerzeń, obsługuje wszystkie powyższe scenariusze -

extension UITabBarController {
    open override var childForStatusBarStyle: UIViewController? {
        return selectedViewController?.childForStatusBarStyle ?? selectedViewController
    }
}

extension UINavigationController {
    open override var childForStatusBarStyle: UIViewController? {
        return topViewController?.childForStatusBarStyle ?? topViewController
    }
}

extension AppRootViewController {
    open override var preferredStatusBarStyle: UIStatusBarStyle {
        return children.first { $0.childForStatusBarStyle != nil }?.childForStatusBarStyle?.preferredStatusBarStyle ?? .default
    }
}
  • Nie potrzebujesz UIViewControllerBasedStatusBarAppearanceklucza, info.plistponieważ domyślnie jest to prawda

Punkty do rozważenia w przypadku bardziej złożonych przepływów

  • W przypadku modalnego przedstawienia nowego przepływu, odłącza się on od istniejącego przepływu w stylu paska stanu. Załóżmy, że prezentujesz NewFlowUIViewControllera następnie dodajesz nowy kontroler nawigacji lub tabBar do NewFlowUIViewController, a następnie dodajesz również rozszerzenie, NewFlowUIViewControlleraby zarządzać dalszym stylem paska stanu kontrolera.

  • Jeśli ustawisz modalPresentationStyle inaczej niż fullScreenpodczas prezentacji modalnej, musisz ustawić wartość modalPresentationCapturesStatusBarAppearancetrue, aby prezentowany kontroler widoku otrzymał kontrolę wyglądu paska stanu.


Doskonała odpowiedź!
Amin Benarieb

3
To źle i psuje się w iOS 13.4. Ponieważ rozszerzenie klas celów C w Swift jest realizowane za pomocą kategorii Celu C. Zastępowanie metod za pomocą kategorii celu C nie jest zalecane i prawdopodobnie się nie powiedzie. Zobacz stackoverflow.com/a/38274660/2438634
Marc Etcheverry

@MarcEtcheverry ta konkretna instancja nie była zła. Faktem jest, że podklasy innych obiektów / protokołów, takich jak UINavigationController, nie miały wcześniejszej ich implementacji w celu konfliktu w dynamicznej wysyłce. W rzeczywistych podklasach nie było żadnych ustawień domyślnych ani implementacji, dlatego był to najczystszy sposób na wdrożenie tego w aplikacji bez tworzenia niepotrzebnej zależności (okresu). Niestety wydaje się, że 13.4 zmieniło to zachowanie. Zgaduję, że za kulisami mają teraz kontrolę lub implementację, która nie istnieje od lat .........
TheCodingArt

8

Rozwiązania iOS 13

UINavigationController jest podklasą UIViewController (kto wiedział 🙃)!

Dlatego prezentując kontrolery widoku osadzone w kontrolerach nawigacji, tak naprawdę nie prezentujesz wbudowanych kontrolerów widoku; prezentujesz kontrolery nawigacyjne! UINavigationController, jako podklasa UIViewControllerdziedziczy preferredStatusBarStyleichildForStatusBarStyle , którą można ustawić według potrzeb.

Każda z poniższych metod powinna działać:

  1. Całkowicie zrezygnuj z trybu ciemnego
    • W Twoim info.plist dodaj następującą właściwość:
      • Klucz - UIUserInterfaceStyle (inaczej. „Styl interfejsu użytkownika”)
      • Wartość - światło
  2. Zastąp preferredStatusBarStylew ciąguUINavigationController

    • preferredStatusBarStyle( doc ) - Preferowany styl paska stanu dla kontrolera widoku
    • Podklasa lub rozszerzenie UINavigationController

      class MyNavigationController: UINavigationController {
          override var preferredStatusBarStyle: UIStatusBarStyle {
              .lightContent
          }
      }

      LUB

      extension UINavigationController {
          open override var preferredStatusBarStyle: UIStatusBarStyle {
              .lightContent
          }
      }
  3. Zastąp childForStatusBarStylew ciąguUINavigationController

    • childForStatusBarStyle( dok ) - Wywoływany, gdy system potrzebuje kontrolera widoku do określania stylu paska stanu
    • Według dokumentacji Apple

      „Jeśli kontroler widoku kontenera wywodzi swój styl paska stanu z jednego ze swoich kontrolerów widoku potomnego, [zastąp tę właściwość] i zwróć ten kontroler widoku potomnego. Jeśli zwrócisz zero lub nie zastąpisz tej metody, użyty zostanie styl paska stanu dla siebie . Jeśli wartość zwracana z tej metody ulegnie zmianie, wywołaj metodę setNeedsStatusBarAppearanceUpdate (). "

    • Innymi słowy, jeśli nie wdrożysz tutaj rozwiązania 3, system wróci do rozwiązania 2 powyżej.
    • Podklasa lub rozszerzenie UINavigationController

      class MyNavigationController: UINavigationController {
          override var childForStatusBarStyle: UIViewController? {
              topViewController
          }
      }

      LUB

      extension UINavigationController {    
          open override var childForStatusBarStyle: UIViewController? {
              topViewController
          }
      }
    • Możesz zwrócić dowolny kontroler widoku, który chcesz powyżej. Polecam jedno z poniższych:

      • topViewController(z UINavigationController) ( dok ) - Kontroler widoku u góry stosu nawigacji
      • visibleViewController(of UINavigationController) ( doc ) - Kontroler widoku skojarzony z aktualnie widocznym widokiem w interfejsie nawigacyjnym (wskazówka: może to obejmować „kontroler widoku, który został przedstawiony modalnie na samym kontrolerze nawigacyjnym”)

Uwaga: Jeśli zdecydujesz się na podklasę UINavigationController, pamiętaj o zastosowaniu tej klasy do kontrolerów nawigacyjnych za pośrednictwem inspektora tożsamości w IB.

PS Mój kod używa składni Swift 5.1 😎


Mój pasek stanu robi się czarny po obróceniu ekranu. Masz pomysł, dlaczego? Dzieje się tak tylko w symulatorze iPada Pro.
Pedro Paulo Amorim

@PedroPauloAmorim, czy możesz podać więcej informacji? Jak prezentowany jest kontroler widoku z góry (modalny, pełny ekran, pokaz)? Czy jest zagnieżdżony w kontrolerze nawigacyjnym? Czy tekst staje się czarny, czy też tło? Co próbujesz osiągnąć
Andrew Kirna,

Ustawiam pasek stanu świateł w całej mojej aplikacji. Dostaje światło w dwóch obrotach, w trzecim ciemnieje i nigdy nie wraca do światła, nawet zmuszając go przerysować. Dzieje się to na symulatorze iPada Pro. Widoki są prezentowane na pełnym ekranie i nie są zagnieżdżone w kontrolerze nawigacyjnym. Tylko tekst ciemnieje.
Pedro Paulo Amorim

Jak ustawiasz pasek stanu świateł w pierwszej kolejności?
Andrew Kirna,

3
Twoje zastąpienie przez rozszerzenie nie jest prawdziwym zastąpieniem. Jest to niebezpieczne niewłaściwe użycie języka. To może się bardzo łatwo złamać.
Sulthan

7

Powyższa odpowiedź @ serenn jest nadal świetna w przypadku UINavigationControllers. Jednak w swift 3 funkcje childViewController zostały zmienione na vars. Zatem UINavigationControllerkod rozszerzenia powinien być:

override open var childViewControllerForStatusBarStyle: UIViewController? {
  return topViewController
}

override open var childViewControllerForStatusBarHidden: UIViewController? {
  return topViewController
}

A następnie w kontrolerze widoku, który powinien dyktować styl paska stanu:

override var preferredStatusBarStyle: UIStatusBarStyle {
   return .lightContent
}

2
To źle i psuje się w iOS 13.4. Ponieważ rozszerzenie klas celów C w Swift jest realizowane za pomocą kategorii Celu C. Zastępowanie metod za pomocą kategorii celu C nie jest zalecane i prawdopodobnie się nie powiedzie. Zobacz stackoverflow.com/a/38274660/2438634
Marc Etcheverry

@MarcEtcheverry ta konkretna instancja nie była zła. Faktem jest, że podklasy innych obiektów / protokołów, takich jak UINavigationController, nie miały wcześniejszej ich implementacji w celu konfliktu w dynamicznej wysyłce. W rzeczywistych podklasach nie było żadnych ustawień domyślnych ani implementacji, dlatego był to najczystszy sposób na wdrożenie tego w aplikacji bez tworzenia niepotrzebnej zależności (okresu). Niestety wydaje się, że 13.4 zmieniło to zachowanie. Zgaduję, że za kulisami mają teraz kontrolę lub implementację, która nie istnieje od lat .........
TheCodingArt


4

UIStatusBarStyle w iOS 7

Pasek stanu w iOS 7 jest przezroczysty, widać za nim widok.

Styl paska stanu odnosi się do wyglądu jego zawartości. W iOS 7 zawartość paska stanu jest ciemna ( UIStatusBarStyleDefault) lub jasna ( UIStatusBarStyleLightContent). Zarówno UIStatusBarStyleBlackTranslucenti UIStatusBarStyleBlackOpaquesą przestarzałe w iOS 7.0. Posługiwać sięUIStatusBarStyleLightContent zamiast tego.

Jak zmienić UIStatusBarStyle

Jeśli poniżej paska stanu znajduje się pasek nawigacji, styl paska stanu zostanie dopasowany do stylu paska nawigacji (UINavigationBar.barStyle ):

W szczególności, jeśli stylem paska nawigacji jest UIBarStyleDefault, stylem paska stanu będzie UIStatusBarStyleDefault; jeśli stylem paska nawigacji jest UIBarStyleBlack, stylem paska stanu będzieUIStatusBarStyleLightContent .

Jeśli pod paskiem stanu nie ma paska nawigacji, styl paska stanu może być kontrolowany i zmieniany przez indywidualny kontroler widoku podczas działania aplikacji.

- [UIViewController preferredStatusBarStyle]to nowa metoda dodana w iOS 7. Można ją zastąpić, aby zwrócić preferowany styl paska stanu:

- (UIStatusBarStyle)preferredStatusBarStyle
  {
      return UIStatusBarStyleLightContent;
  }

Jeśli styl paska stanu powinien być kontrolowany przez kontroler widoku potomnego zamiast siebie, zastąp -[UIViewController childViewControllerForStatusBarStyle] aby zwrócić ten kontroler widoku potomnego.

Jeśli wolisz zrezygnować z tego zachowania i ustawić styl paska stanu za pomocą -[UIApplication statusBarStyle]metody, dodaj UIViewControllerBasedStatusBarAppearanceklucz do pliku aplikacji Info.plisti nadaj mu wartość NIE.


3

Jeśli ktoś używa kontrolera nawigacyjnego i chce, aby wszystkie kontrolery nawigacyjne miały czarny styl, możesz napisać rozszerzenie do UINavigationController w ten sposób w Swift 3 i będzie ono dotyczyło wszystkich kontrolerów nawigacyjnych (zamiast przypisywać je do jednego kontrolera na czas).

extension UINavigationController {

    override open func viewDidLoad() {
        super.viewDidLoad()

        self.navigationBar.barStyle = UIBarStyle.black
    }

}

1
Ale co, jeśli mój pasek nawigacji jest ukryty?
Slavcho

1
Ponieważ potrzebuję ukryć nawigację i widoczny pasek stanu.
Slavcho

1

W Swift dla dowolnego rodzaju UIViewController:

W twoim AppDelegatezestawie:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    window!.rootViewController = myRootController
    return true
}

myRootControllermoże być dowolnego rodzaju UIViewController, np . UITabBarControllerlub UINavigationController.

Następnie zastąp ten kontroler root w następujący sposób:

class RootController: UIViewController {
    override func preferredStatusBarStyle() -> UIStatusBarStyle {
        return .LightContent
    }
}

Spowoduje to zmianę wyglądu paska stanu w całej aplikacji, ponieważ kontroler root ponosi wyłączną odpowiedzialność za wygląd paska stanu.

Pamiętaj, aby ustawić właściwość View controller-based status bar appearancena TAK, Info.plistaby ta funkcja działała (co jest ustawieniem domyślnym).


@Jak to działa w swift3?
samolot

1

Rozwiązanie Swift 3 iOS 10:

override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent
 }

1

Większość odpowiedzi nie obejmuje dobrej implementacji childViewControllerForStatusBarStylemetody dla UINavigationController. Zgodnie z moim doświadczeniem powinieneś poradzić sobie z takimi przypadkami, jak wyświetlanie kontrolera przezroczystego nad kontrolerem nawigacyjnym. W takich przypadkach powinieneś przekazać kontrolę swojemu kontrolerowi modalnemu ( visibleViewController), ale nie wtedy, gdy znika.

override var childViewControllerForStatusBarStyle: UIViewController? {
  var childViewController = visibleViewController
  if let controller = childViewController, controller.isBeingDismissed {
    childViewController = topViewController
  }
  return childViewController?.childViewControllerForStatusBarStyle ?? childViewController
}

1

W moim przypadku przypadkowo przedstawiłem View / Navigation Controller as UIModalPresentationStyle.overFullScreen, co powoduje, że preferredStatusBarStylenie jestem wywoływany. Po przełączeniu z powrotem UIModalPresentationStyle.fullScreenwszystko działa.


1

Jak w iOS 13.4 preferredStatusBarStylemetoda wUINavigationController kategorii nie będzie wywoływana, swizzling wydaje się być jedyną opcją bez potrzeby używania podklasy.

Przykład:

Nagłówek kategorii:

@interface UINavigationController (StatusBarStyle)
+ (void)setUseLightStatusBarStyle;
@end

Realizacja:

#import "UINavigationController+StatusBarStyle.h"
#import <objc/runtime.h>

@implementation UINavigationController (StatusBarStyle)

void (^swizzle)(Class, SEL, SEL) = ^(Class c, SEL orig, SEL new){
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, newMethod);
};

+ (void)setUseLightStatusBarStyle {
    swizzle(self.class, @selector(preferredStatusBarStyle), @selector(_light_preferredStatusBarStyle));
}

- (UIStatusBarStyle)_light_preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}    
@end

Zastosowanie w AppDelegate.h:

#import "UINavigationController+StatusBarStyle.h"

[UINavigationController setUseLightStatusBarStyle];

0

Oto moja metoda rozwiązania tego problemu.

Zdefiniuj protokół o nazwie AGViewControllerAppearance .

AGViewControllerAppearance.h

#import <Foundation/Foundation.h>

@protocol AGViewControllerAppearance <NSObject>

@optional

- (BOOL)showsStatusBar;
- (BOOL)animatesStatusBarVisibility;
- (UIStatusBarStyle)preferredStatusBarStyle;
- (UIStatusBarAnimation)prefferedStatusBarAnimation;

@end

Zdefiniuj kategorię w UIViewController o nazwie Upgrade .

UIViewController + Upgrade.h

#import <UIKit/UIKit.h>

@interface UIViewController (Upgrade)

//
//  Replacements
//

- (void)upgradedViewWillAppear:(BOOL)animated;

@end

UIViewController + Upgrade.m

#import "UIViewController+Upgrade.h"

#import <objc/runtime.h>

#import "AGViewControllerAppearance.h" // This is the appearance protocol

@implementation UIViewController (Upgrade)

+ (void)load
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wselector"
    Method viewWillAppear = class_getInstanceMethod(self, @selector(viewWillAppear:));
#pragma clang diagnostic pop
    Method upgradedViewWillAppear = class_getInstanceMethod(self, @selector(upgradedViewWillAppear:));
    method_exchangeImplementations(viewWillAppear, upgradedViewWillAppear);
}

#pragma mark - Implementation

- (void)upgradedViewWillAppear:(BOOL)animated
{
    //
    //  Call the original message (it may be a little confusing that we're
    //  calling the 'same' method, but we're actually calling the original one :) )
    //

    [self upgradedViewWillAppear:animated];

    //
    //  Implementation
    //

    if ([self conformsToProtocol:@protocol(AGViewControllerAppearance)])
    {
        UIViewController <AGViewControllerAppearance> *viewControllerConformingToAppearance =
        (UIViewController <AGViewControllerAppearance> *)self;

        //
        //  Status bar
        //

        if ([viewControllerConformingToAppearance respondsToSelector:@selector(preferredStatusBarStyle)])
        {
            BOOL shouldAnimate = YES;

            if ([viewControllerConformingToAppearance respondsToSelector:@selector(animatesStatusBarVisibility)])
            {
                shouldAnimate = [viewControllerConformingToAppearance animatesStatusBarVisibility];
            }

            [[UIApplication sharedApplication] setStatusBarStyle:[viewControllerConformingToAppearance preferredStatusBarStyle]
                                                        animated:shouldAnimate];
        }

        if ([viewControllerConformingToAppearance respondsToSelector:@selector(showsStatusBar)])
        {
            UIStatusBarAnimation animation = UIStatusBarAnimationSlide;

            if ([viewControllerConformingToAppearance respondsToSelector:@selector(prefferedStatusBarAnimation)])
            {
                animation = [viewControllerConformingToAppearance prefferedStatusBarAnimation];
            }

            [[UIApplication sharedApplication] setStatusBarHidden:(! [viewControllerConformingToAppearance showsStatusBar])
                                                    withAnimation:animation];
        }
    }
}

@end

Czas powiedzieć, że kontroler widoku implementuje wygląd AGViewController protokół .

Przykład:

@interface XYSampleViewController () <AGViewControllerAppearance>

... the rest of the interface

@end

Oczywiście, można zaimplementować resztę metod ( showsStatusBar , animatesStatusBarVisibility , prefferedStatusBarAnimation ) od protokołu i UIViewController + Upgrade zrobi właściwego dostosowania na podstawie wartości dostarczonych przez nich.


0

Jeśli ktoś napotka ten problem z UISearchController. Po prostu utwórz nową podklasę UISearchController, a następnie dodaj kod poniżej do tej klasy:

override func preferredStatusBarStyle() -> UIStatusBarStyle {
    return .LightContent
}

0

Pamiętaj, że podczas korzystania z self.navigationController.navigationBar.barStyle = UIBarStyleBlack; rozwiązania

koniecznie przejdź do swojej listy i ustaw „Wyświetl wygląd paska stanu kontrolera” na TAK. Jeśli NIE, to nie będzie działać.


Ustawienie UIViewControllerBasedStatusBarAppearance na YES w liście projektowej zrobiło dla mnie różnicę. Zapomniałem o tym.
filo

0

Od Xcode 11.4, zastępowanie preferredStatusBarStyle właściwości w rozszerzeniu UINavigationController nie działa już, ponieważ nie zostanie wywołane.

Ustawianie barStylez navigationBardo .blackprac rzeczywiście ale to doda niepożądane skutki uboczne, jeśli dodać subviews do navigationBar które mogą mieć różny wygląd w trybie jasnym i ciemnym. Ponieważ ustawienie na barStyleczarny spowoduje, że userInterfaceStylewidok osadzony w pasku nawigacji będzie zawsze miał userInterfaceStyle.darkniezależnie od userInterfaceStyleaplikacji.

Właściwym rozwiązaniem, które wymyślam, jest dodanie podklasy UINavigationControlleri zastąpienie go preferredStatusBarStyle. Jeśli następnie użyjesz tego niestandardowego kontrolera UINavigationController dla wszystkich swoich widoków, będziesz po stronie zapisywania.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.