Błąd aplikacji na iOS - nie można dodać siebie jako widoku podrzędnego

157

Otrzymałem ten raport o awarii, ale nie wiem, jak go debugować.

Fatal Exception NSInvalidArgumentException
Can't add self as subview
0 ...    CoreFoundation  __exceptionPreprocess + 130
1    libobjc.A.dylib     objc_exception_throw + 38
2    CoreFoundation  -[NSException initWithCoder:]
3    UIKit   -[UIView(Internal) _addSubview:positioned:relativeTo:] + 110
4    UIKit   -[UIView(Hierarchy) addSubview:] + 30
5    UIKit   __53-[_UINavigationParallaxTransition animateTransition:]_block_invoke + 1196
6    UIKit   +[UIView(Animation) performWithoutAnimation:] + 72
7    UIKit   -[_UINavigationParallaxTransition animateTransition:] + 732
8    UIKit   -[UINavigationController _startCustomTransition:] + 2616
9    UIKit   -[UINavigationController _startDeferredTransitionIfNeeded:] + 418
10   UIKit   -[UINavigationController __viewWillLayoutSubviews] + 44
11   UIKit   -[UILayoutContainerView layoutSubviews] + 184
12   UIKit   -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 346
13   QuartzCore  -[CALayer layoutSublayers] + 142
14   QuartzCore  CA::Layer::layout_if_needed(CA::Transaction*) + 350
15   QuartzCore  CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 16
16   QuartzCore  CA::Context::commit_transaction(CA::Transaction*) + 228
17   QuartzCore  CA::Transaction::commit() + 314
18   QuartzCore  CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 56

Wersja iOS to 7.0.3. Czy ktoś doświadczył tej dziwnej katastrofy?

AKTUALIZACJA:

Nie wiem, gdzie w moim kodzie spowodowało to awarię, więc nie mogę tutaj umieścić kodu, przepraszam.

Druga aktualizacja

Zobacz odpowiedź poniżej.

Arnol
źródło
3
Czy możesz pokazać nam swój kod?
David Gölzhäuser
43
Przepraszam, ale nie rozumiem twojej przesadnej reakcji. Błąd stosu jest jednoznaczny z problemem. Więc najpierw możesz pozwolić użytkownikowi wprowadzić więcej kodu zgodnie z jego zapytaniem (tylko 1 godzinę zadanego pytania i poprosisz o natychmiastowe zamknięcie). Po drugie otrzymałem głos przeciw bez powodu, ponieważ moja odpowiedź jest jasna. Pytanie brzmi: „Czy ktoś doświadczył tej dziwnej awarii?”. I powiedziałem, dlaczego to dostał. Nawet jeśli nie jest konkretnie zlokalizowany w swoim kodzie.
Tancrede Chazallet
9
To pytanie jest poprawne. użytkownik nie może podać dokładnego kodu błędu w tej sytuacji. ponieważ nie wie, w którym widoku kontrolera coś się nie udaje
Ravindra Bagale
16
Używamy Crashlytics i mamy ponad 30 użytkowników, którzy spowodowali awarię naszej aplikacji z komunikatem „Nie można dodać siebie jako widoku podrzędnego”. Oczywiście nie mamy kodu, który próbuje dodać siebie jako widok podrzędny. Ze śladu wstecznego nie ma żadnego odniesienia do naszej aplikacji.
Richie Hyatt
49
Głosowanie za ponownym otwarciem; ludzie, którzy go zamykają, najwyraźniej nie robią zbyt wiele dla programistów iOS, ponieważ jest to częsty problem wprowadzany przez iOS7 i zabijający całą masę aplikacji, które były w porządku na iOS6 (widziałem to w wielu projektach różnych firm). Szkoda, że ​​to pytanie jest hitem w Google, ale kilka krótkowzrocznych osób je zamknęło.
Adam

Odpowiedzi:

51

Spekuluję na podstawie czegoś podobnego, co ostatnio debugowałem ... jeśli wepchniesz (lub pop) kontroler widoku za pomocą Animated: TAK, nie kończy się to od razu, a złe rzeczy się zdarzają, jeśli wykonasz kolejne naciśnięcie lub pop przed animacją kończy. Możesz łatwo sprawdzić, czy tak jest, tymczasowo zmieniając operacje Push i Pop na Animated: NO (tak, aby zakończyły się synchronicznie) i sprawdzając, czy to eliminuje awarię. Jeśli rzeczywiście jest to twój problem i chcesz ponownie włączyć animację, właściwą strategią jest zaimplementowanie protokołu UINavigationControllerDelegate. Obejmuje to następującą metodę, która jest wywoływana po zakończeniu animacji:

navigationController:didShowViewController:animated:

Zasadniczo chcesz przenieść część kodu zgodnie z potrzebą do tej metody, aby upewnić się, że żadne inne akcje, które mogłyby spowodować zmianę stosu NavigationController, nie wystąpią, dopóki animacja nie zostanie zakończona, a stos jest gotowy do dalszych zmian.

RobP
źródło
Kiedyś o iOS 4 - coś podobnego widziałem w jednej z naszych aplikacji - IIRC, jeśli włączysz animację, a następnie natychmiast uruchomisz animację, kod interfejsu użytkownika zostanie poważnie zepsuty. Skończyło się na zmianie, aby nigdy nie wykonywać dwóch animowanych operacji push / pop z powrotem do tyłu. Oczywiście cała podstawowa logika została od tego czasu przepisana, ale nietrudno uwierzyć, że podobnego błędu nadal nie ma.
Hot Licks
Miałem ten sam problem. W moim przypadku stało się tak, ponieważ aplikacja wykonała instrukcję, która zmienia interfejs użytkownika nowego kontrolera widoku [newViewController setLabelTitle:...]tuż po wywołaniu pushViewController za pomocą. Animated:YES.I rozwiązałem przeniesienie metody setLabelTitle do viewDidLoad na newViewController. Dzięki, że dałeś mi wskazówkę.
jeprubio
Cieszę się, że pomogło! Dobrze, że przeniesienie kodu do nowego ViewController jest również opcją, jeśli wiesz, która to będzie klasa. Coraz bardziej uważam, że przydatne jest przechwytywanie różnych metod protokołu UINavigationControllerDelegate. Odkryłem, że w iOS8 zdarzenia uruchamiają się w różnej kolejności, a niektóre rzeczy, które kiedyś były mniej lub bardziej synchroniczne, teraz wracają szybko, ale planuj rzeczy do wykonania w tle asynchronicznie, tworząc wiele nowych błędów czasowych, takich jak te. Dzięki, Apple!
RobP
14

Zaczęliśmy również pojawiać się ten problem i było bardzo prawdopodobne, że nasz był spowodowany tym samym problemem.

W naszym przypadku musieliśmy w niektórych przypadkach pobierać dane z zaplecza, co oznaczało, że użytkownik mógł coś dotknąć, a następnie nastąpiło niewielkie opóźnienie przed wystąpieniem push nawigacji. Jeśli użytkownik szybko stukał, może skończyć z dwoma wypchnięciami nawigacji z tego samego kontrolera widoku, co wywołało ten wyjątek.

Nasze rozwiązanie to kategoria w UINavigationController, która zapobiega pchnięciom / wyskakiwaniu, chyba że górny vc jest taki sam z danego punktu w czasie.

plik .h:

@interface UINavigationController (SafePushing)

- (id)navigationLock; ///< Obtain "lock" for pushing onto the navigation controller

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock; ///< Uses a horizontal slide transition. Has no effect if the view controller is already in the stack. Has no effect if navigationLock is not the current lock.
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock; ///< Pops view controllers until the one specified is on top. Returns the popped controllers. Has no effect if navigationLock is not the current lock.
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated navigationLock:(id)navigationLock; ///< Pops until there's only a single view controller left on the stack. Returns the popped controllers. Has no effect if navigationLock is not the current lock.

@end

plik .m:

@implementation UINavigationController (SafePushing)

- (id)navigationLock
{
    return self.topViewController;
}

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock
{
    if (!navigationLock || self.topViewController == navigationLock) 
        [self pushViewController:viewController animated:animated];
}

- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated navigationLock:(id)navigationLock
{
    if (!navigationLock || self.topViewController == navigationLock)
        return [self popToRootViewControllerAnimated:animated];
    return @[];
}

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock
{
    if (!navigationLock || self.topViewController == navigationLock)
        return [self popToViewController:viewController animated:animated];
    return @[];
}

@end

Jak dotąd wydaje się, że rozwiązało to problem za nas. Przykład:

id lock = _dataViewController.navigationController.navigationLock;
[[MyApi sharedClient] getUserProfile:_user.id success:^(MyUser *user) {
    ProfileViewController *pvc = [[ProfileViewController alloc] initWithUser:user];
    [_dataViewController.navigationController pushViewController:pvc animated:YES navigationLock:lock];
}];

Zasadniczo obowiązuje zasada: przed jakimikolwiek opóźnieniami niezwiązanymi z użytkownikiem należy pobrać blokadę z odpowiedniego kontrolera nawigacyjnego i uwzględnić ją w wywołaniu funkcji push / pop.

Słowo „blokada” może być nieco słabo sformułowane, ponieważ może sugerować, że występuje jakaś forma blokady, która wymaga odblokowania, ale ponieważ nigdzie nie ma metody „odblokowania”, prawdopodobnie jest w porządku.

(Na marginesie, „opóźnienia niezwiązane z użytkownikiem” to wszelkie opóźnienia powodowane przez kod, tj. Wszystko asynchroniczne. Użytkownicy dotykający kontrolera nawigacyjnego, który jest animowany, nie liczą się i nie ma potrzeby wykonywania funkcji navigationLock: wersja dla tych przypadków.)

Kalle
źródło
Skoro powiedziałeś, że wypróbowujesz to rozwiązanie, czy rozwiązało ono problem?
Mike D
Jak dotąd tak. Problem nie powrócił. Zaktualizuję odpowiedź.
Kalle
4
Użyłem zmodyfikowanej wersji opartej na twojej: gist.github.com/mdewolfe/9369751 . Wygląda na to, że to naprawiło.
Mike D
2
@Kalle To rozwiązanie działa w trybie push / pop. Ale jak rozwiązać ten błąd, jeśli używam Segue?
Geek
@Kadle Czy możesz mi pomóc to wdrożyć? Zobacz stackoverflow.com/q/23247713/1323014 THX
Marckaraujo Kwietnia
12

Ten kod rozwiązuje problem: https://gist.github.com/nonamelive/9334458

Używa prywatnego interfejsu API, ale mogę potwierdzić, że jest bezpieczny w App Store. (Jedna z moich aplikacji korzystających z tego kodu została zatwierdzona przez App Store).

@interface UINavigationController (DMNavigationController)

- (void)didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;

@end

@interface DMNavigationController ()

@property (nonatomic, assign) BOOL shouldIgnorePushingViewControllers;

@end

@implementation DMNavigationViewController

#pragma mark - Push

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if (!self.shouldIgnorePushingViewControllers)
    {
        [super pushViewController:viewController animated:animated];
    }

    self.shouldIgnorePushingViewControllers = YES;
}

#pragma mark - Private API

// This is confirmed to be App Store safe.
// If you feel uncomfortable to use Private API, you could also use the delegate method navigationController:didShowViewController:animated:.
- (void)didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [super didShowViewController:viewController animated:animated];
    self.shouldIgnorePushingViewControllers = NO;
}
nonamelive
źródło
Jak dotąd było to najlepsze rozwiązanie, z niektórymi innymi nadal losowo otrzymywałbym problem z podwójnym naciśnięciem lub zamrożony kontroler nawigacyjny.
blueice
Ten kod nie kompiluje się dla mnie, czy czegoś brakuje?
Maxime B,
8

Opiszę więcej szczegółów na temat tej awarii w mojej aplikacji i oznaczę jako odpowiedź.

Moja aplikacja ma UINavigationController z kontrolerem głównym to UITableViewController, który zawiera listę obiektów notatek. Obiekt notatki ma właściwość content w formacie html. Wybierz notatkę, która trafi do kontrolera szczegółowego.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    //get note object
    DetailViewController *controller = [[DetailViewController alloc] initWithNote:note];
    [self.navigationController pushViewController:controller animated:YES];
}

Szczegółowy kontroler

Ten kontroler ma UIWebView, wyświetla zawartość notatki przekazaną z kontrolera głównego.

- (void)viewDidLoad
{
    ...
    [_webView loadHTMLString:note.content baseURL:nil];
    ...
}

Ten kontroler jest delegatem kontrolki widoku internetowego. Jeśli notatka zawiera łącza, stuknij łącze, aby przejść do przeglądarki internetowej w aplikacji.

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    WebBrowserViewController *browserController = [[WebBrowserViewController alloc] init];
    browserController.startupURL = request.URL;
    [self.navigationController pushViewController:webViewController animated:YES];
    return NO;
}

Powyższy raport o awarii otrzymywałem codziennie. Nie wiem, gdzie w moim kodzie spowodowała awarię. Po kilku badaniach z pomocą użytkownika, w końcu udało mi się naprawić tę awarię. Ta zawartość html spowoduje awarię:

...
<iframe src="http://google.com"></iframe>
...

W metodzie viewDidLoad kontrolera detail załadowałem ten html do kontrolki webview, zaraz po tym powyższa metoda delegata została wywołana natychmiast z request.URL jest źródłem iframe (google.com). Ta metoda delegata wywołuje metodę pushViewController w trybie viewDidLoad => awaria!

Naprawiłem tę awarię, sprawdzając navigationType:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    if (navigationType != UIWebViewNavigationTypeOther)
    {
        //go to web browser controller
    }
}

Mam nadzieję że to pomoże

Arnol
źródło
1
Czy nie byłoby dobrym rozwiązaniem przesunięcie kontrolera bez animacji po wywołaniu z viewDidLoad?
Rivera
6

Miałem ten sam problem, co po prostu zadziałało, to zmiana Animated: Yes na Animated: No.

Wygląda na to, że problem był spowodowany tym, że animacja nie kończyła się na czas.

Mam nadzieję, że to komuś pomoże.

Lion789
źródło
3

Aby odtworzyć ten błąd, spróbuj wypchnąć dwa kontrolery widoku w tym samym czasie. Albo pchanie i popychanie jednocześnie. Przykład:

wprowadź opis obrazu tutaj Stworzyłem kategorię, która przechwytuje te połączenia i zabezpiecza je, upewniając się, że żadne inne naciśnięcia nie mają miejsca, gdy jedno jest w toku. Po prostu skopiuj kod do swojego projektu, a dzięki zamianie metod będziesz gotowy.

#import "UINavigationController+Consistent.h"
#import <objc/runtime.h>
/// This char is used to add storage for the isPushingViewController property.
static char const * const ObjectTagKey = "ObjectTag";

@interface UINavigationController ()
@property (readwrite,getter = isViewTransitionInProgress) BOOL viewTransitionInProgress;

@end

@implementation UINavigationController (Consistent)

- (void)setViewTransitionInProgress:(BOOL)property {
    NSNumber *number = [NSNumber numberWithBool:property];
    objc_setAssociatedObject(self, ObjectTagKey, number , OBJC_ASSOCIATION_RETAIN);
}


- (BOOL)isViewTransitionInProgress {
    NSNumber *number = objc_getAssociatedObject(self, ObjectTagKey);

    return [number boolValue];
}


#pragma mark - Intercept Pop, Push, PopToRootVC
/// @name Intercept Pop, Push, PopToRootVC

- (NSArray *)safePopToRootViewControllerAnimated:(BOOL)animated {
    if (self.viewTransitionInProgress) return nil;
    if (animated) {
        self.viewTransitionInProgress = YES;
    }
    //-- This is not a recursion, due to method swizzling the call below calls the original  method.
    return [self safePopToRootViewControllerAnimated:animated];

}


- (NSArray *)safePopToViewController:(UIViewController *)viewController animated:(BOOL)animated {
    if (self.viewTransitionInProgress) return nil;
    if (animated) {
        self.viewTransitionInProgress = YES;
    }
    //-- This is not a recursion, due to method swizzling the call below calls the original  method.
    return [self safePopToViewController:viewController animated:animated];
}


- (UIViewController *)safePopViewControllerAnimated:(BOOL)animated {
    if (self.viewTransitionInProgress) return nil;
    if (animated) {
        self.viewTransitionInProgress = YES;
    }
    //-- This is not a recursion, due to method swizzling the call below calls the original  method.
    return [self safePopViewControllerAnimated:animated];
}



- (void)safePushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    self.delegate = self;
    //-- If we are already pushing a view controller, we dont push another one.
    if (self.isViewTransitionInProgress == NO) {
        //-- This is not a recursion, due to method swizzling the call below calls the original  method.
        [self safePushViewController:viewController animated:animated];
        if (animated) {
            self.viewTransitionInProgress = YES;
        }
    }
}


// This is confirmed to be App Store safe.
// If you feel uncomfortable to use Private API, you could also use the delegate method navigationController:didShowViewController:animated:.
- (void)safeDidShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    //-- This is not a recursion. Due to method swizzling this is calling the original method.
    [self safeDidShowViewController:viewController animated:animated];
    self.viewTransitionInProgress = NO;
}


// If the user doesnt complete the swipe-to-go-back gesture, we need to intercept it and set the flag to NO again.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    id<UIViewControllerTransitionCoordinator> tc = navigationController.topViewController.transitionCoordinator;
    [tc notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        self.viewTransitionInProgress = NO;
        //--Reenable swipe back gesture.
        self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController;
        [self.interactivePopGestureRecognizer setEnabled:YES];
    }];
    //-- Method swizzling wont work in the case of a delegate so:
    //-- forward this method to the original delegate if there is one different than ourselves.
    if (navigationController.delegate != self) {
        [navigationController.delegate navigationController:navigationController
                                     willShowViewController:viewController
                                                   animated:animated];
    }
}


+ (void)load {
    //-- Exchange the original implementation with our custom one.
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(pushViewController:animated:)), class_getInstanceMethod(self, @selector(safePushViewController:animated:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(didShowViewController:animated:)), class_getInstanceMethod(self, @selector(safeDidShowViewController:animated:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(popViewControllerAnimated:)), class_getInstanceMethod(self, @selector(safePopViewControllerAnimated:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(popToRootViewControllerAnimated:)), class_getInstanceMethod(self, @selector(safePopToRootViewControllerAnimated:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(popToViewController:animated:)), class_getInstanceMethod(self, @selector(safePopToViewController:animated:)));
}

@end
dan
źródło
Jeden problem z tego rozwiązania jest to, że jeśli zadzwonisz popToRootViewControllerlub popToViewController:kiedy jesteś już na kontrolerze widoku głównego lub na viewController być pojawiło się, to didShowViewControllernie będzie się nazywać i będzie tkwić w viewTransitionInProgress.
divergio
1
Czy możesz wyjaśnić te wiersze: self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController; [self.interactivePopGestureRecognizer setEnabled:YES]; Kiedy aparat rozpoznawania został wyłączony? A skąd wiesz, jaki powinien być delegat? Z tymi liniami, dla mnie przerywa to gest popu po jednym wyskoku.
divergio
Próbowałem to zaimplementować i po chwili blokuje kontroler nawigacji, prawdopodobnie z powodu tego, o czym wspomniał @divergio.
blueice
2

Właśnie doświadczyłem tego problemu. Pokażę Ci mój kod:

override func viewDidLoad() { 
  super.viewDidLoad()

  //First, I create a UIView
  let firstFrame = CGRect(x: 50, y: 70, height: 200, width: 200)
  let firstView = UIView(frame: firstFrame)
  firstView.addBackgroundColor = UIColor.yellow
  view.addSubview(firstView) 

  //Now, I want to add a subview inside firstView
  let secondFrame = CGRect(x: 20, y:50, height: 15, width: 35)
  let secondView = UIView(frame: secondFrame)
  secondView.addBackgroundColor = UIColor.green
  firstView.addSubView(firstView)
 }

Błąd pojawia się z powodu tej linii:

firstView.addSubView(firstView)

Nie możesz dodać siebie do widoku podrzędnego. Zmieniłem linię kodu na:

firstView.addSubView(secondView)

Błąd zniknął i mogłem zobaczyć oba widoki. Pomyślałem, że to pomoże każdemu, kto chce zobaczyć przykład.

halapgos1
źródło
Próbowałem też tego podejścia, ale ślad stosu byłby inny i faktycznie pokazywałby wiersz twojego kodu powodujący awarię. Uważam, że problem źródłowy różni się od pytania.
Ben
1

Wyszukaj w swoim kodzie „addSubview”.

W jednym z miejsc, które wywołałeś tę metodę, próbowałeś dodać widok do własnej tablicy widoków podrzędnych za pomocą tej metody.

Na przykład:

[self.view addSubview:self.view];

Lub:

[self.myLabel addSubview:self.myLabel];
Michał Shatz
źródło
Cieszymy się, że znalazłeś swój błąd. Teraz dokładnie rozumiem, dlaczego otrzymałeś komunikat „Nie można dodać siebie jako podwidoku”. W momencie, gdy twój View2 był głównym kontrolerem widoku twojego kontrolera nawigacyjnego, pchnąłeś View2, co spowodowało to: w [View2.view addSubview:View2.view]ten sposób dodając self jako podwidok.
Michal Shatz
1

Myślę, że wypychanie / wyskakiwanie kontrolerów widoku z animacją w dowolnym momencie powinno być idealnie w porządku, a SDK powinien łaskawie obsługiwać kolejkę wywołań za nas.

Dlatego tak się nie dzieje i wszystkie rozwiązania starają się ignorować kolejne wypychania, co można uznać za błąd, ponieważ ostateczny stos nawigacji nie jest zgodny z zamierzeniem kodu.

Zamiast tego zaimplementowałem kolejkę połączeń push:

// SafeNavigationController.h

@interface SafeNavigationController : UINavigationController
@end

 

// SafeNavigationController.m

#define timeToWaitBetweenAnimations 0.5

@interface SafeNavigationController ()

@property (nonatomic, strong) NSMutableArray * controllersQueue;
@property (nonatomic)         BOOL animateLastQueuedController;
@property (nonatomic)         BOOL pushScheduled;
@property (nonatomic, strong) NSDate * lastAnimatedPushDate;

@end

@implementation SafeNavigationController

- (void)awakeFromNib
{
    [super awakeFromNib];

    self.controllersQueue = [NSMutableArray array];
}

- (void)pushViewController:(UIViewController *)viewController
                  animated:(BOOL)animated
{
    [self.controllersQueue addObject:viewController];
    self.animateLastQueuedController = animated;

    if (self.pushScheduled)
        return;

    // Wait for push animation to finish
    NSTimeInterval timeToWait = self.lastAnimatedPushDate ? timeToWaitBetweenAnimations + [self.lastAnimatedPushDate timeIntervalSinceNow] : 0.0;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((timeToWait > 0.0 ? timeToWait : 0.0) * NSEC_PER_SEC)),
                   dispatch_get_main_queue(), ^
                   {
                       [self pushQueuedControllers];

                       self.lastAnimatedPushDate = self.animateLastQueuedController ? [NSDate date] : nil;
                       self.pushScheduled = NO;
                   });
    self.pushScheduled = YES;
}

- (void)pushQueuedControllers
{
    for (NSInteger index = 0; index < (NSInteger)self.controllersQueue.count - 1; index++)
    {
        [super pushViewController:self.controllersQueue[index]
                         animated:NO];
    }
    [super pushViewController:self.controllersQueue.lastObject
                     animated:self.animateLastQueuedController];

    [self.controllersQueue removeAllObjects];
}

@end

Nie obsługuje mieszanych kolejek push i pop, ale jest dobrym początkiem do naprawienia większości naszych awarii.

Streszczenie: https://gist.github.com/rivera-ernesto/0bc628be1e24ff5704ae

Rivera
źródło
Próbowałem twojego rozwiązania, które wydaje się bardzo dobre, ale mam problem. Naciskając 2 kontrolery widoku z animowanym NIE jeden po drugim, bardzo krótko widzę pierwszy. To się wcześniej nie zdarzyło. Masz jakiś pomysł, co mogę zrobić, aby to naprawić?
Jan
Próbuję zbudować projekt, który może konsekwentnie powodować tego rodzaju awarie (mój prawdziwy projekt otrzymuje takie raporty o awariach). Zrobiłem prostą aplikację z kontrolerem nawigacji, kontrolerem głównym i przyciskiem, który natychmiast wypycha 4 nowe kontrolery widoku na stos nawigacyjny, a następnie wyskakuje z ostatniego. Wydaje się, że bez żadnej specjalnej podklasy lub czegokolwiek to działa dobrze. Czy Apple ostatnio to naprawiło?
Cruinh
1

Przepraszam za spóźnienie na przyjęcie. Niedawno miałem ten problem, w którym mój pasek nawigacyjny przechodzi w stan uszkodzony z powodu jednoczesnego naciskania więcej niż jednego kontrolera widoku. Dzieje się tak, ponieważ drugi kontroler widoku jest wypychany, gdy pierwszy kontroler widoku nadal jest animowany. Biorąc pod uwagę odpowiedź nonamelive, wymyśliłem proste rozwiązanie, które działa w moim przypadku. Wystarczy UINavigationControllerpodklasować i zastąpić metodę pushViewController oraz sprawdzić, czy poprzednia animacja kontrolera widoku została jeszcze zakończona. Możesz posłuchać zakończenia animacji, ustawiając swoją klasę jako delegata UINavigationControllerDelegatei ustawiając delegata naself .

Wrzuciłem tutaj streszczenie aby uprościć sprawę.

Po prostu upewnij się, że ustawiłeś tę nową klasę jako NavigationController w swoim scenorysie.

nikhil.thakkar
źródło
Jak dotąd wydaje się, że naprawiono awarie aplikacji, nad którą pracowałem ... ponadto rozwiązanie jest dość proste i jasne: pierwsza animacja kontrolera widoku nie była jeszcze ukończona. Osoby mające ten sam problem powinny to sprawdzić.
alasker
0

Bazując na @RobP, stworzyłem podklasę UINavigationController , aby zapobiec takim problemom. Obsługuje pchanie i / lub popping i możesz bezpiecznie wykonać:

[self.navigationController pushViewController:vc1 animated:YES];
[self.navigationController pushViewController:vc2 animated:YES];
[self.navigationController pushViewController:vc3 animated:YES];
[self.navigationController popViewControllerAnimated:YES];

Jeśli flaga 'acceptConflictingCommands' to prawda (domyślnie), użytkownik zobaczy animowane wypychanie vc1, vc2, vc3, a następnie zobaczy animowane wyskakiwanie vc3. Jeśli „acceptConflictingCommands” ma wartość false, wszystkie żądania push / pop będą odrzucane, dopóki vc1 nie zostanie w pełni przekazane - stąd pozostałe 3 wywołania zostaną odrzucone.

hris.to
źródło
czy te polecenia są rzeczywiście sprzeczne? Właśnie utworzyłem szybki nowy projekt, aby zobaczyć, jak dochodzi do awarii, używając kodu takiego jak powyżej (ale w Swift), i faktycznie wykonywał każde naciśnięcie i pop, wszystko po kolei. jedna po drugiej. Bez wypadku. Bez używania żadnych podklas. po prostu zwykły kontroler UINavigationController firmy Apple.
Cruinh
Rzeczywiście zawieszał się z ObjC i iOS 7. Nie mogę potwierdzić, czy nadal występuje. Czy na pewno wykonujesz polecenia z animated:trueflagą?
hris. Do
Tak, użyłem animowanej: prawdziwej flagi.
Cruinh
0

Rozwiązanie Nonamelive jest niesamowite. Ale jeśli nie chcesz używać prywatnego interfejsu API, możesz po prostu zastosować tę UINavigationControllerDelegatemetodę lub zmienić animowany YESna NO. Oto przykład kodu, który możesz dziedziczyć. Mam nadzieję, że to pomocne :)

https://github.com/antrix1989/ANNavigationController

NSKevin
źródło
0

Dużo przeszukiwałem ten problem, być może wciskając dwa lub więcej VC w tym samym czasie, co powoduje problem z animacją wypychania, możesz odnieść się do tego: Can't Add Self as Subview 崩溃 解决 办法

po prostu upewnij się, że w tym samym czasie jest jeden VC na postępie przejścia , powodzenia.

MichaelMao
źródło
0

Czasami omyłkowo próbowałeś dodać widok do własnego widoku.

halfView.addSubview(halfView)

zmień to na swój widok podrzędny.

halfView.addSubview(favView)
Vinoth Vino
źródło
0

Ja też napotkałem ten problem. Kiedy przeprowadziłem analizę dziennika Firebase, odkryłem, że ten problem występuje tylko wtedy, gdy aplikacja jest uruchamiana na zimno. Więc napisałem demo które może odtworzyć tę awarię.

.

Odkryłem również, że gdy wyświetlany jest główny kontroler widoku okna, wykonanie wielu naciśnięć nie spowoduje ponownie tego samego problemu. (Możesz skomentować testColdStartUp (rootNav) w AppDelegate.swift i odkomentować komentarz testColdStartUp () w ViewController.swift)

ps: Przeanalizowałem miejsce tej awarii w mojej aplikacji. Gdy użytkownik kliknie powiadomienie push, aby uruchomić aplikację na zimno, aplikacja nadal znajduje się na stronie uruchamiania i klika kolejne naciśnięcie, aby przejść. W tej chwili w aplikacji może pojawić się awaria. Moje obecne Rozwiązanie polega na zapisaniu w pamięci podręcznej zimnego startu łącza push lub uniwersalnego, aby otworzyć stronę skoku aplikacji, poczekaniu na wyświetlenie kontrolera rootview, a następnie opóźnieniu wykonania.

Jader Yang
źródło
-2

wypróbuj nawigację metodą opóźnienia, aby ukończyć ostatnią animację nawigacji,

[self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>]

Naeem Paracha
źródło
-2

Widok nie może być dodany jako widok podrzędny.

Widoki zachowują hierarchię nadrzędny-podrzędny, więc jeśli dodasz widok jako widok podrzędny sam w sobie, przejdzie on przez wyjątek.

jeśli klasa to UIViewController, aby uzyskać jej widok, użyj self.view.

jeśli klasa jest klasą UIView, aby uzyskać jej widok, użyj self.

Mradul Kumar
źródło
-3

nie możesz dodać siebie jako widoku podrzędnego, jeśli ma to być klasa UiViewController. możesz dodać siebie jako widok podrzędny, jeśli ma to być klasa UiView.

user1533983
źródło
-9

Jeśli chcesz dodać widok podrzędny do widoku, możesz to zrobić w ten sposób;

UIView *mainview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)]; //Creats the mainview
    UIView *subview = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; //Creates the subview, you can use any kind of Views (UIImageView, UIWebView, UIView…)

    [mainview addSubview:subview]; //Adds subview to mainview
David Gölzhäuser
źródło
Dobra robota, to niezły fragment kodu. Czy możesz mi teraz powiedzieć, co to ma wspólnego z tym pytaniem i jak je rozwiązuje?
Popeye
@Popeye Czy masz lepszy pomysł?
David Gölzhäuser
Nie, ponieważ nie dostarczyli wystarczających informacji / kodu, aby odtworzyć problem. Więc nie ma sposobu, aby odpowiedzieć na to pytanie, po prostu wygląda na to, że mówisz im, jak zrobić coś, co nie ma nic wspólnego z ich problemem.
Popeye
2
Myślę, że o wiele bardziej pomocne było dla nich zamknięcie problemu. Rozumiem! +1 dla Davida G za faktyczną próbę pomocy komuś na StackOverflow. Chciałbym móc -1 głosów na zakończenie !!! To wciąż się dzieje dla użytkowników i może to być błąd w iOS7 dla wszystkiego, co wiemy. Dlatego tylko dlatego, że ktoś nie może opublikować kodu naruszającego prawa, nie oznacza, że ​​pytanie jest nieprawidłowe i wartościowe dla innych użytkowników. Nawet jeśli chodzi tylko o to, by zobaczyć, że inni ludzie widzą ten sam problem bez żadnego logicznego powodu, dla którego go widzą. -rrh
Richie Hyatt