Zmiana przycisku Wstecz w iOS 7 wyłącza przesuwanie, aby przejść wstecz


81

Mam aplikację na iOS 7, w której ustawiam niestandardowy przycisk Wstecz w następujący sposób:

    UIImage *backButtonImage = [UIImage imageNamed:@"back-button"];
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];

    [backButton setImage:backButtonImage forState:UIControlStateNormal];
    backButton.frame = CGRectMake(0, 0, 20, 20);

    [backButton addTarget:self
                   action:@selector(popViewController)
         forControlEvents:UIControlEventTouchUpInside];

    UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    viewController.navigationItem.leftBarButtonItem = backBarButtonItem;

Ale to wyłącza gest „przesuń palcem od lewej do prawej” w iOS 7, aby przejść do poprzedniego kontrolera. Czy ktoś wie, jak mogę ustawić niestandardowy przycisk i nadal mieć ten gest włączony?

EDYCJA: Próbowałem zamiast tego ustawić viewController.navigationItem.backBarButtonItem, ale nie wydaje się, aby wyświetlał mój niestandardowy obraz.


Nie znalazłem jeszcze odpowiedniego rozwiązania tego problemu ?? Czy jest ktoś, kto znalazł dobre rozwiązanie i wyjaśnił, dlaczego to działa?
— user1010819

Co powiesz na korzystanie z dobrze wykonanej biblioteki innej firmy: SwipeBack ?
— devxoul,

Odpowiedzi:


82

WAŻNE: to jest hack. Poleciłbym przyjrzeć się tej odpowiedzi .

Dzwonię pod następujący numer po przypisaniu leftBarButtonItemmi działał:

self.navigationController.interactivePopGestureRecognizer.delegate = self;

Edycja: to nie działa, jeśli wywoływane są initmetody. Powinien zostać wywołany viewDidLoadlub podobnymi metodami.


Czy ustawiasz to na kontrolerze widoku? czy kontroler nawigacji? Mam ten sam problem, ale to nie działa?
— Kevin Renskers

1
@mixedCase Po pobraniu przykładowego projektu rozumiem teraz Twój problem. Kod działa, o ile zawartość widoku kolekcji nie przekracza poziomej szerokości kontrolera widoku. Jednak gdy tylko widok kolekcji stanie się przewijalny w poziomie, zastępuje on InteractivePopGestureRecognizer. Zobaczę, czy uda mi się znaleźć obejście.
— Paul Hunter

8
Mam problem z tym kodem. Działa niedługo, po 5-10 przesunięciach w tył VC zawiesza się. Jakieś rozwiązanie?
— Timur Bernikovich

1
Timur Bernikowich Ja przeżywam to samo. Czy znalazłeś jakieś powody?
— user1010819

4
Ustawienie delegata w celu selfusunięcia go z obiektu klasy _UINavigationInteractiveTransition. Obowiązkiem tego obiektu jest upewnienie się, że kontroler nawigacji nie zostanie poproszony o wyskakiwanie podczas przejścia. Wciąż badam, czy można włączyć ten gest, czy nie, gdy przycisk Wstecz jest niestandardowy.
— Saltymule

56

Jeśli to możliwe, użyj właściwości backIndicatorImage i backIndicatorTransitionMaskImage elementu UINavigationBar. Ustawienie ich na UIAppearanceProxy może łatwo modyfikować zachowanie w całej aplikacji. Problem polega na tym, że możesz ustawić je tylko na ios 7, ale to działa, ponieważ i tak możesz używać tylko gestu pop na ios 7. Twoja normalna stylizacja ios 6 może pozostać nienaruszona.

UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance];
//the appearanceProxy returns NO, so ask the class directly
if ([[UINavigationBar class] instancesRespondToSelector:@selector(setBackIndicatorImage:)])
{
    appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:@"back"];
    appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];
    //sets back button color
    appearanceNavigationBar.tintColor = [UIColor whiteColor];
}else{
    //do ios 6 customization
}

Próba manipulowania delegatem InteractivePopGestureRecognizer doprowadzi do wielu problemów.


4
Dzięki Dan, to naprawdę ważne i powinno być akceptowaną odpowiedzią. Po prostu ponownie przydzielając delegata, otwierasz się na DUŻO dziwnych zachowań. Szczególnie, gdy użytkownicy próbują przesunąć palcem z powrotem na topViewController.
— Chris Wagner

1
Jest to rozwiązanie zakładające, że wszystko, co chcesz zrobić, to zmienić obraz przycisku Wstecz. Ale co, jeśli chcesz zmienić tekst przycisku Wstecz i / lub akcję wykonywaną po kliknięciu przycisku Wstecz?
— user102008

W tym momencie lepiej byłoby ustawić właściwość UINavigationItem.leftBarButtonItem. Istnieje wiele odpowiedzi, które można znaleźć, wyszukując leftBarButtonItem w google lub stackoverflow.
— Saltymule

Jeśli chcesz ograniczyć zmianę wyglądu do własnego interfejsu użytkownika i nie wpływać na paski nawigacji, które są na przykład tworzone przez ABPeoplePickerNavigationControllerCiebie, możesz użyć niestandardowej UINavigationControllerpodklasy:[[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorImage:[UIImage imageNamed:@"btn_back_arrow"]]; [[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"btn_back_arrow_highlighted"]];
— tom

2
Niestety to nie działa na iOS8. Przycisk Wstecz nie zmienia swojego wyglądu na mój niestandardowy obraz.
— Lensflare

29

Widziałem to rozwiązanie http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/, które podklasy UINavigationController. Jest to lepsze rozwiązanie, ponieważ obsługuje przypadek, w którym przesuwasz palcem, zanim kontroler jest na miejscu - co powoduje awarię.

Oprócz tego zauważyłem, że jeśli wykonasz przesunięcie palcem na głównym kontrolerze widoku (po naciśnięciu jednego i z powrotem) interfejs użytkownika przestaje odpowiadać (również ten sam problem w odpowiedzi powyżej).

Więc kod w podklasie UINavigationController powinien wyglądać tak:

@implementation NavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak NavigationController *weakSelf = self;

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.delegate = weakSelf;
        self.delegate = weakSelf;
    }
}

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    // Hijack the push method to disable the gesture
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    [super pushViewController:viewController animated:animated];
}

#pragma mark - UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate {
    // Enable the gesture again once the new controller is shown
    self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1);
}

@end

1
miał te same problemy z przesuwaniem palcem po głównym kontrolerze widoku, dzięki!
— Michael Rose

NAJLEPSZE ROZWIĄZANIE NA CAŁEJ STRONIE. DZIAŁA IDEALNIE DOBRZE. DZIĘKI
— Shahid Iqbal

19

używam

[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:@"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"nav_back.png"]];

[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];

2
Widziałem komentarze gdzie indziej, że powoduje to problemy w trybie 64-bitowym z powodu błędu w kodzie Apple w chwili obecnej - pomyślałem, że opublikuję ostrzeżenie
— Peter Johnson

@PeterJohnson jaki rodzaj błędu?
— art-divin

Po prostu najlepsze rozwiązanie!
— Igotit

Najprostsze rozwiązanie w historii.
— tounaobun

6

Ukrywam też przycisk Wstecz, zastępując go niestandardowym leftBarItem.
Usunięcie delegata InteractivePopGestureRecognizer po akcji wypychania działało u mnie:

[self.navigationController pushViewController:vcToPush animated:YES];

// Enabling iOS 7 screen-edge-pan-gesture for pop action
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}

4
Jednym z problemów z tą metodą jest to, że jeśli wykonasz panoramowanie krawędzi w widoku głównym, zablokuje to interfejs użytkownika. Napotkałem to, ponieważ wypycham wiele wystąpień tego samego widoku do stosu nawigacji.
— Nikola Lajic,

@MrNickBarker Dzięki za poinformowanie mnie! Czy mógłbyś opisać dokładny scenariusz? Nie mogłem go odtworzyć, przesuwając główny kontroler widoku
— avishic

4
Ustawiałem to w metodzie viewDidLoad. Moim ostatecznym rozwiązaniem było ustawienie innej klasy jako delegata, która po prostu zwraca wartość true dla „gestRecognizerShouldBegin”, jeśli w stosie nawigacji jest więcej niż jeden kontroler widoku.
— Nikola Lajic,

MrNickBarker wszelkie powody, dla których się zawiesza, doświadczam tego samego
— user1010819

6
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

To pochodzi z http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks , ale powoduje kilka błędów:

  1. Wciśnij inny viewController do navigationController, przesuwając palcem od lewej krawędzi ekranu;
  2. Lub przesuń palcem od lewej krawędzi ekranu, gdy topViewController wyskakuje z navigationController;

np. gdy wyświetla się rootViewController z navigationController, przesuń palcem od lewej krawędzi ekranu i dotknij czegoś (SZYBKO), aby wypchnąć anotherViewController do navigationController, a następnie

  • RootViewController nie odpowiada na żadne zdarzenie dotykowe;
  • AnotherViewController nie będzie wyświetlane;
  • Ponownie przesuń palcem od krawędzi ekranu, zostanie wyświetlony inny kontroler;
  • Stuknij niestandardowy przycisk Wstecz, aby wyświetlić anotherViewController, awaria!

Więc musisz zaimplementować UIGestureRecognizerDelegatemetodę w następujący sposób self.navigationController.interactivePopGestureRecognizer.delegate:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
        return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
    }
    return YES;
}

1
SwipeBack jest rozwiązaniem tych problemów.
— devxoul

6

Oto szybka wersja odpowiedzi Nicka H247

class NavigationController: UINavigationController {
  override func viewDidLoad() {
    super.viewDidLoad()
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.delegate = self
      delegate = self
    }
  }

  override func pushViewController(_ viewController: UIViewController, animated: Bool) {
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.isEnabled = false
    }
    super.pushViewController(viewController, animated: animated)
  }
}

extension NavigationController: UINavigationControllerDelegate {
  func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1)
  }
}

extension NavigationController: UIGestureRecognizerDelegate {}

3

Próbować self.navigationController.interactivePopGestureRecognizer.enabled = YES;


Nie, jest już włączone. Problem polega na tym, że jego delegat nie zezwala na uruchomienie funkcji rozpoznawania gestów. Dodałem tutaj moje rozwiązanie jako kolejną odpowiedź.
— avishic

avishic czy mógłbyś wyjaśnić, dlaczego ustawienie delegata zadziała? .. Utknąłem przy tym samym problemie.
— user1010819

1

Nie napisałem tego, ale poniższy blog bardzo pomógł i rozwiązał moje problemy z niestandardowym przyciskiem nawigacji:

http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/

Podsumowując, implementuje niestandardowy kontroler UINavigationController, który używa delegata gestu pop. Bardzo czysty i przenośny!

Kod:

@interface CBNavigationController : UINavigationController <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
@end

@implementation CBNavigationController

- (void)viewDidLoad
{
  __weak CBNavigationController *weakSelf = self;

  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
  {
    self.interactivePopGestureRecognizer.delegate = weakSelf;
    self.delegate = weakSelf;
  }
}

// Hijack the push method to disable the gesture

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = NO;

  [super pushViewController:viewController animated:animated];
}

#pragma mark UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
  // Enable the gesture again once the new controller is shown

  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = YES;
}

Edytować. Dodano naprawę problemów, gdy użytkownik próbuje przesunąć palcem w lewo na głównym kontrolerze widoku:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] &&
        self.topViewController == [self.viewControllers firstObject] &&
        gestureRecognizer == self.interactivePopGestureRecognizer) {

        return NO;
    }

    return YES;
}

Interfejs użytkownika zawiesza się, jeśli przesuniemy palcem w lewo na głównym kontrolerze widoku kontrolera nawigacji.
— JohnVanDijk

@JohnVanDijk Zmieniłem odpowiedź z poprawką, którą, jak sądzę, zaimplementowałem, aby rozwiązać ten problem. Minęło trochę czasu, ale ma sens. Zasadniczo, jeśli kontroler widoku z góry jest głównym kontrolerem widoku, nie będziemy odpowiadać na „InteractivePopGestureRecognizer”
— kgaidis

Ukrywa mój guzik z powrotem
— Mohammed Hussain

1

RootView

override func viewDidAppear(_ animated: Bool) {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}

ChildView

override func viewDidLoad() {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
    self.navigationController?.interactivePopGestureRecognizer?.delegate = self
} 

extension ChildViewController: UIGestureRecognizerDelegate {}

1

Użyj tej logiki, aby włączyć lub wyłączyć gest przesunięcia.

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        if (self.navigationController.viewControllers.count > 1)
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = YES;
        }
        else
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        }
    }
}

0

Miałem podobny problem, gdy przypisywałem bieżący kontroler widoku jako delegata dla interaktywnego gestu pop, ale przerywałem ten gest w przypadku wszystkich wypchniętych widoków lub widoków pod widokiem w stosie nawigacji. Sposób, w jaki to rozwiązałem, polegał na ustawieniu delegata -viewDidAppear, a następnie ustawieniu go na zero -viewWillDisappear. To pozwoliło moim innym widokom działać poprawnie.


0

Wyobraź sobie, że używamy domyślnego szablonu projektu głównego / szczegółów firmy Apple, w którym wzorzec jest kontrolerem widoku tabeli, a dotknięcie go spowoduje wyświetlenie kontrolera widoku szczegółów.

Chcemy dostosować przycisk Wstecz, który pojawia się w kontrolerze widoku szczegółów. Oto jak dostosować obraz , kolor obrazu , tekst , kolor tekstu i czcionkę przycisku Wstecz.


Aby globalnie zmienić obraz, kolor obrazu, kolor tekstu lub czcionkę, umieść następujące elementy w lokalizacji, która jest wywoływana przed utworzeniem któregokolwiek z kontrolerów widoku (np. application:didFinishLaunchingWithOptions:Jest to dobre miejsce).

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UINavigationBar* navigationBarAppearance = [UINavigationBar appearance];

    // change the back button, using default tint color
    navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:@"back"];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the back button, using the color inside the original image
    navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:@"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the tint color of everything in a navigation bar
    navigationBarAppearance.tintColor = [UIColor greenColor];

    // change the font in all toolbar buttons
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };

    [[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];

    return YES;
}

Uwaga, możesz użyć, appearanceWhenContainedIn:aby mieć większą kontrolę nad kontrolerami widoku, na które wpływają te zmiany, ale pamiętaj, że nie możesz ich przekazać [DetailViewController class], ponieważ jest zawarty w UINavigationController, a nie w Twoim DetailViewController. Oznacza to, że jeśli chcesz mieć większą kontrolę nad tym, czego dotyczy problem, musisz utworzyć podklasę UINavigationController.

Aby dostosować tekst lub czcionkę / kolor określonego elementu przycisku Wstecz, musisz to zrobić w MasterViewController (nie w DetailViewController!). Wydaje się to nieintuicyjne, ponieważ przycisk pojawia się na DetailViewController. Jednak gdy zrozumiesz, że sposobem dostosowywania jest ustawienie właściwości w navigationItem, zaczyna to mieć więcej sensu.

- (void)viewDidLoad { // MASTER view controller
    [super viewDidLoad];

    UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:@"Testing"
                                                                   style:UIBarButtonItemStylePlain
                                                                  target:nil
                                                                  action:nil];
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };
    [buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
    self.navigationItem.backBarButtonItem = buttonItem;
}

Uwaga: próba ustawienia atrybutu titleTextAttributes po ustawieniu self.navigationItem.backBarButtonItem nie wydaje się działać, dlatego należy je ustawić przed przypisaniem wartości do tej właściwości.


0

Utwórz klasę „TTNavigationViewController”, która jest podklasą „UINavigationController”, i utwórz istniejący kontroler nawigacji tej klasy w scenorysie / klasie, przykładowy kod w klasie -

    class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.setNavigationBarHidden(true, animated: false)

    // enable slide-back
    if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
        self.interactivePopGestureRecognizer?.isEnabled = true
        self.interactivePopGestureRecognizer?.delegate  = self
    }
}

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}}
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.