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.

lehn0058
źródło
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.

Paul Hunter
źródło
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.

Saltymule
źródło
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
Nick H247
źródło
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];
Albert Chu
źródło
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;
}
avishic
źródło
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;
}
Panie Ming
źródło
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 {}
duan
źródło
3

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

ilya n.
źródło
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;
}
kgaidis
źródło
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 {}
PW486
źródło
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;
        }
    }
}
Muhammad Naeem Paracha
źródło
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.

Bill Burgess
źródło
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.

Rozsądne
źródło
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
}}
Prakash Raj
źródło