Erreur d'application iOS - Impossible de s'ajouter en tant que sous-vue

157

J'ai reçu ce rapport d'erreur, mais je ne sais pas comment le déboguer.

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

La version iOS est 7.0.3. Quelqu'un a-t-il vécu ce crash étrange?

METTRE À JOUR:

Je ne sais pas où dans mon code a causé ce plantage, donc je ne peux pas poster le code ici, désolé.

Deuxième MISE À JOUR

Voir la réponse ci-dessous.

Arnol
la source
3
Pouvez-vous nous montrer votre code?
David Gölzhäuser
43
Désolé mais je ne comprends pas votre réaction excessive. L'erreur de pile est claire sur le problème. Donc d'abord, vous pouvez laisser l'utilisateur mettre plus de code comme demandé (seulement 1h la question posée et vous demandez de le fermer immédiatement). Deuxièmement, j'ai reçu un vote défavorable sans raison car ma réponse est claire. La question est "Quelqu'un a-t-il vécu ce crash étrange?". Et j'ai dit pourquoi il avait ça. Même s'il n'est pas spécifiquement localisé dans son code.
Tancrede Chazallet
9
Cette question est une bonne. l'utilisateur ne peut pas donner le code d'erreur exact dans cette situation. parce qu'il ne sait pas dans quel contrôleur de vue quelque chose ne va pas
Ravindra Bagale
16
Nous utilisons Crashlytics et avons plus de 30 utilisateurs qui ont planté notre application avec le "Impossible de s'ajouter en tant que sous-vue", bien sûr, nous n'avons pas de code qui essaie de s'ajouter en tant que sous-vue. De la backtrace, il n'y a aucune référence à notre application.
Richie Hyatt
49
Voter pour rouvrir; les gens qui le ferment ne font pas beaucoup de développement iOS, apparemment, car il s'agit d'un problème courant introduit par iOS7 et tuant tout un tas d'applications qui fonctionnaient bien sur iOS6 (je l'ai vu sur plusieurs projets de différentes sociétés). C'est dommage que cette question soit un succès sur Google, mais quelques personnes myopes l'ont fermée.
Adam

Réponses:

51

Je spécule sur la base de quelque chose de similaire que j'ai débogué récemment ... si vous poussez (ou pop) un contrôleur de vue avec Animated: OUI cela ne se termine pas tout de suite, et de mauvaises choses se produisent si vous faites un autre push ou pop avant l'animation se termine. Vous pouvez facilement tester si c'est effectivement le cas en modifiant temporairement vos opérations Push et Pop en Animated: NO (afin qu'elles se terminent de manière synchrone) et voir si cela élimine le crash. Si c'est effectivement votre problème et que vous souhaitez réactiver l'animation, la stratégie correcte consiste à implémenter le protocole UINavigationControllerDelegate. Cela inclut la méthode suivante, qui est appelée une fois l'animation terminée:

navigationController:didShowViewController:animated:

Fondamentalement, vous souhaitez déplacer du code selon les besoins dans cette méthode pour vous assurer qu'aucune autre action susceptible de provoquer une modification de la pile NavigationController ne se produira jusqu'à ce que l'animation soit terminée et que la pile soit prête pour d'autres modifications.

RobP
la source
Il y a longtemps à propos d'iOS 4, j'ai vu quelque chose de similaire dans l'une de nos applications - IIRC, si vous apparaissiez animé puis que vous étiez immédiatement animé, le code de l'interface utilisateur deviendrait très sale. J'ai fini par changer pour ne jamais faire deux opérations push / pop animées dos à dos. Bien sûr, toute la logique sous-jacente a été réécrite depuis, mais il n'est pas difficile de croire qu'un bug similaire n'est pas encore là.
Hot Licks
J'ai eu le même problème. Dans mon cas, cela s'est produit parce que l'application a exécuté une instruction qui modifie l' [newViewController setLabelTitle:...]interface utilisateur du nouveau contrôleur de vue juste après avoir appelé pushViewController avec Animated:YES.Et j'ai résolu le déplacement de la méthode setLabelTitle vers viewDidLoad sur le newViewController. Merci de m'avoir donné l'indice.
jeprubio
Heureux que cela ait aidé! Bon point que déplacer le code vers le nouveau ViewController est également une option si vous savez de quelle classe il s'agira. De plus en plus, je trouve utile de saisir les différentes méthodes du protocole UINavigationControllerDelegate, dans tous les cas. Et j'ai constaté que dans iOS8, les événements se déclenchent dans des ordres différents, et certaines choses qui étaient plus ou moins synchrones reviennent maintenant rapidement mais planifient les choses pour qu'elles soient faites en arrière-plan de manière asynchrone, créant de nombreux nouveaux bogues de synchronisation comme ceux-ci. Merci, Apple!
RobP
14

Nous avons également commencé à avoir ce problème, et il est fort probable que le nôtre soit causé par le même problème.

Dans notre cas, nous devions extraire des données du back-end dans certains cas, ce qui signifiait qu'un utilisateur pouvait appuyer sur quelque chose, puis il y avait un léger délai avant que la poussée de navigation se produise. Si un utilisateur tapait rapidement, il pourrait se retrouver avec deux poussées de navigation du même contrôleur de vue, ce qui a déclenché cette exception.

Notre solution est une catégorie sur le UINavigationController qui empêche les poussées / pops à moins que le vc supérieur ne soit le même à partir d'un moment donné.

fichier .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

Fichier .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

Jusqu'à présent, cela semble avoir résolu le problème pour nous. Exemple:

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];
}];

Fondamentalement, la règle est la suivante: avant tout retard non lié à l'utilisateur, saisissez un verrou du contrôleur de navigation concerné et incluez-le dans l'appel à push / pop.

Le mot "verrouiller" peut être une formulation légèrement médiocre car il peut insinuer qu'il existe une forme de verrouillage qui doit être déverrouillée, mais comme il n'y a pas de méthode de "déverrouillage" nulle part, c'est probablement correct.

(En remarque, les "retards non liés à l'utilisateur" sont tous les retards que le code provoque, c'est-à-dire tout ce qui est asynchrone. Les utilisateurs qui tapent sur un contrôleur de navigation qui est poussé de manière animée ne comptent pas et il n'est pas nécessaire de faire le navigationLock: version pour ceux-ci cas.)

Kalle
la source
Puisque vous avez dit que vous essayiez cette solution, a-t-elle résolu le problème pour vous?
Mike D
Jusqu'ici, oui. Le problème n'a pas refait surface. Je mettrai à jour la réponse.
Kalle
4
J'ai utilisé une version modifiée basée sur la vôtre: gist.github.com/mdewolfe/9369751 . On dirait qu'il l'a corrigé.
Mike D
2
@Kalle Cette solution fonctionne pour push / pop. Mais comment résoudre cette erreur si j'utilise segue?
Geek
@Kadle Pouvez-vous m'aider à mettre en œuvre cela? Regardez stackoverflow.com/q/23247713/1323014 THX
Marckaraujo
12

Ce code résout le problème: https://gist.github.com/nonamelive/9334458

Il utilise une API privée, mais je peux confirmer que c'est sûr App Store. (Une de mes applications utilisant ce code a été approuvée par l'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;
}
nonamélif
la source
Cela a été la meilleure solution jusqu'à présent, avec certaines des autres, soit j'obtiendrais encore au hasard le problème de double poussée ou j'obtiendrais un contrôleur de navigation gelé.
blueice
Ce code ne se compile pas pour moi, il manque quelque chose?
Maxime B
8

Je décrirai plus de détails sur ce crash dans mon application et marquerai ceci comme répondu.

Mon application a un UINavigationController avec le contrôleur racine est un UITableViewController qui contient une liste d'objets de note. L'objet note a une propriété content en html. Sélectionnez une note qui ira au contrôleur de détail.

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

Contrôleur de détail

Ce contrôleur a un UIWebView, affiche le contenu de la note transmis par le contrôleur racine.

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

Ce contrôleur est le délégué du contrôle Webview. Si la note contient des liens, appuyez sur un lien pour accéder au navigateur Web intégré à l'application.

- (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;
}

J'ai reçu le rapport de plantage ci-dessus tous les jours. Je ne sais pas où dans mon code a causé ce plantage. Après quelques recherches avec l'aide d'un utilisateur, j'ai finalement pu résoudre ce crash. Ce contenu html provoquera le crash:

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

Dans la méthode viewDidLoad du contrôleur de détail, j'ai chargé ce code HTML dans le contrôle Webview, juste après cela, la méthode de délégation ci-dessus a été appelée immédiatement avec request.URL est la source de l'iframe (google.com). Cette méthode déléguée appelle la méthode pushViewController pendant que dans viewDidLoad => crash!

J'ai corrigé ce crash en vérifiant le navigationType:

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

J'espère que cela t'aides

Arnol
la source
1
Ne serait-ce pas une bonne option de pousser le contrôleur sans animation lorsqu'il est appelé depuis viewDidLoad?
Rivera
6

J'ai eu le même problème, ce qui a simplement fonctionné pour moi était de changer Animé: Oui en Animé: Non.

Il semble que le problème soit dû au fait que l'animation ne s'est pas terminée à temps.

J'espère que cela aide quelqu'un.

Lion789
la source
3

Pour reproduire ce bogue, essayez de pousser deux contrôleurs de vue en même temps. Ou pousser et sauter en même temps. Exemple:

entrez la description de l'image ici J'ai créé une catégorie qui intercepte ces appels et les rend sûrs en m'assurant qu'aucune autre poussée ne se produit pendant qu'un est en cours. Copiez simplement le code dans votre projet et en raison du changement de méthode, vous serez prêt à partir.

#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
la source
Un problème avec cette solution est que si vous appelez popToRootViewControllerou popToViewController:lorsque vous êtes déjà sur le contrôleur de vue racine ou sur le viewController, il didShowViewControllerne sera pas appelé et il sera bloqué viewTransitionInProgress.
divergio
1
Pouvez-vous expliquer ces lignes: self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController; [self.interactivePopGestureRecognizer setEnabled:YES]; Quand le module de reconnaissance a-t-il été désactivé? Et comment savez-vous ce que devrait être le délégué? Avec ces lignes, pour moi, cela brise le geste pop après avoir sauté une fois.
divergio
J'ai essayé de l'implémenter et après un certain temps, cela verrouille le contrôleur de navigation, probablement à cause de ce que @divergio a mentionné.
blueice
2

Je viens de rencontrer ce problème également. Laissez-moi vous montrer mon code:

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)
 }

L'erreur survient à cause de cette ligne:

firstView.addSubView(firstView)

Vous ne pouvez pas vous ajouter à la sous-vue. J'ai changé la ligne de code en:

firstView.addSubView(secondView)

L'erreur a disparu et j'ai pu voir les deux vues. Je pensais juste que cela aiderait tous ceux qui voulaient voir un exemple.

halapgos1
la source
J'ai également essayé cette approche mais le stacktrace serait différent et afficherait en fait la ligne de votre code provoquant le crash. Je pense que le problème de la source est différent de la question.
Ben
1

Recherchez dans votre code "addSubview".

Dans l'un des endroits où vous avez appelé cette méthode, vous avez essayé d'ajouter une vue à son propre tableau de sous-vues à l'aide de cette méthode.

Par exemple:

[self.view addSubview:self.view];

Ou:

[self.myLabel addSubview:self.myLabel];
Michal Shatz
la source
Heureux d'apprendre que vous avez trouvé votre erreur, et maintenant je comprends exactement pourquoi vous avez reçu le message "Impossible de s'ajouter en tant que sous-vue". À un moment où votre View2 était le contrôleur de vue racine de votre contrôleur de navigation, vous avez poussé View2, ce qui a causé ceci: [View2.view addSubview:View2.view]ainsi, l'ajout de soi en tant que sous-vue.
Michal Shatz
1

Je pense que pousser / sauter des contrôleurs de vue avec une animation à tout moment devrait être parfaitement bien et que le SDK devrait gérer gracieusement la file d'attente des appels pour nous.

Par conséquent, ce n'est pas le cas et toutes les solutions essaient d'ignorer les poussées ultérieures, ce qui pourrait être considéré comme un bogue puisque la pile de navigation finale n'est pas ce que le code voulait.

J'ai mis en place une file d'attente d'appels push à la place:

// 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

Il ne gère pas les files d'attente mixtes de push et de pop, mais c'est un bon démarreur pour résoudre la plupart de nos plantages.

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

Rivera
la source
J'ai essayé votre solution qui semble très bonne, mais j'ai un problème. En poussant 2 contrôleurs de vue avec NON animé l'un après l'autre, je vois très brièvement le premier. Cela ne s'est pas produit avant. Une idée de ce que je peux faire pour y remédier?
Jan
J'essaie de construire un projet qui peut systématiquement provoquer ce type de plantage (mon vrai projet reçoit des rapports d'incident comme celui-ci). J'ai fait une application simple avec un contrôleur de navigation, un contrôleur racine et un bouton qui pousse immédiatement 4 nouveaux contrôleurs de vue sur la pile de navigation, puis fait apparaître le dernier. Sans aucun sous-classement spécial ou quoi que ce soit, cela semble fonctionner correctement. Apple a-t-il résolu ce problème récemment?
Cruinh
1

Désolé d'être en retard pour la fête. J'ai récemment eu ce problème dans lequel ma barre de navigation entre dans un état corrompu en poussant plus d'un contrôleur de vue en même temps. Cela se produit car l'autre contrôleur de vue est poussé alors que le premier contrôleur de vue est toujours en cours d'animation. Faisant allusion à la réponse non amélive, j'ai trouvé ma solution simple qui fonctionne dans mon cas. Vous avez juste besoin de sous-classerUINavigationController -classer et de remplacer la méthode pushViewController et de vérifier si l'animation précédente du contrôleur de vue est encore terminée. Vous pouvez écouter la fin de l'animation en faisant de votre classe un délégué UINavigationControllerDelegateet en définissant le délégué sur self.

J'ai téléchargé l'essentiel ici pour simplifier les choses.

Assurez-vous simplement de définir cette nouvelle classe comme NavigationController dans votre storyboard.

nikhil.thakkar
la source
Jusqu'à présent, il semble avoir corrigé les plantages sur l'application sur laquelle je travaillais ... de plus, la solution est assez simple et claire sur le point: la première animation du contrôleur de vue n'était pas encore terminée. Les personnes ayant le même problème devraient vérifier cela.
alasker
0

Basé sur le bon indice @RobP, j'ai créé la sous-classe UINavigationController afin d'éviter de tels problèmes. Il gère la poussée et / ou le popping et vous pouvez exécuter en toute sécurité:

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

Si le drapeau 'acceptConflictingCommands' est vrai (par défaut), l'utilisateur verra un push animé de vc1, vc2, vc3 puis verra un pop-up animé de vc3. Si 'acceptConflictingCommands' est faux, toutes les requêtes push / pop seront rejetées jusqu'à ce que vc1 soit complètement poussé - donc les 3 autres appels seront rejetés.

hris.to
la source
ces commandes sont-elles réellement en conflit? Je viens de lancer un nouveau projet rapide pour voir ce crash se produire, en utilisant le code comme vous avez ci-dessus (mais dans Swift), et il exécutait en fait chaque push et le pop, le tout dans l'ordre. l'un après l'autre. Pas de crash. Sans utiliser de sous-classes. juste UINavigationController régulier d'Apple.
Cruinh
Il plantait en effet avec ObjC et iOS 7. Je ne peux pas confirmer si cela se produit encore maintenant. Êtes-vous sûr d'exécuter les commandes avec animated:trueindicateur?
hris au
Oui, j'utilisais le drapeau animé: vrai.
Cruinh
0

La solution de nonamelive est géniale. Mais si vous ne souhaitez pas utiliser l'API privée, vous pouvez simplement UINavigationControllerDelegateutiliser la méthode ou changer l'animation YESenNO . Voici un exemple de code, vous pouvez en hériter. J'espère que c'est utile :)

https://github.com/antrix1989/ANNavigationController

NSKevin
la source
0

J'avais beaucoup cherché ce problème, cela poussait peut-être deux VC ou plus en même temps, ce qui causait le problème d'animation de poussée, vous pouvez vous référer à ceci: Impossible de s'ajouter en tant que sous-vue 崩溃 解决 办法

assurez-vous simplement qu'il y a un VC sur la progression de la transition en même temps , bonne chance.

MichaelMao
la source
0

Parfois, vous avez essayé par erreur d'ajouter une vue à sa propre vue.

halfView.addSubview(halfView)

changez ceci en votre vue secondaire.

halfView.addSubview(favView)
Vinoth Vino
la source
0

J'ai également rencontré ce problème. Lorsque j'ai effectué l'analyse du journal Firebase, j'ai constaté que ce problème ne se produit que lorsque l'application est démarrée à froid. J'ai donc écrit une démo qui peut reproduire ce crash.

.

J'ai également constaté que lorsque le viewcontroller racine de la fenêtre est affiché, effectuer plusieurs poussées ne causera plus le même problème. (Vous pouvez commenter testColdStartUp (rootNav) dans AppDelegate.swift et décommenter le commentaire testColdStartUp () dans ViewController.swift)

ps: J'ai analysé la scène de ce crash dans mon application. Lorsque l'utilisateur clique sur la notification push pour démarrer l'application à froid, l'application est toujours sur la page de lancement et clique sur un autre push pour sauter. À ce moment, l'application peut apparaître le Crash. Mon courant La solution consiste à mettre en cache le démarrage à froid push ou Universal Link pour ouvrir la page de saut de l'application, attendre que le rootviewcontroller s'affiche, puis retarder l'exécution.

Jader Yang
la source
-2

essayez votre navigation en utilisant la méthode de retard, pour terminer la dernière animation de navigation,

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

Naeem Paracha
la source
-2

Une vue ne peut pas être ajoutée en tant que sous-vue en elle-même.

Les vues maintiennent une hiérarchie parent-enfant, donc si vous ajoutez une vue en tant que sous-vue en elle-même, elle passera par exception.

si une classe est UIViewController, vous utilisez self.view pour obtenir sa vue.

si une classe est UIView Class, vous utilisez self pour obtenir sa vue.

Mradul Kumar
la source
-3

vous ne pouvez pas ajouter self en tant que sous-vue s'il s'agit d'une classe UiViewController. vous pouvez ajouter self en tant que sous-vue s'il s'agit d'une classe UiView.

user1533983
la source
-9

Si vous souhaitez ajouter une sous-vue à une vue, vous pouvez le faire comme ceci;

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
la source
Bravo, c'est un bon morceau de code. Maintenant, pouvez-vous me dire ce que cela a à voir avec cette question et comment cela la résout?
Popeye
@Popeye Avez-vous une meilleure idée?
David Gölzhäuser
Non, car ils n'ont pas fourni suffisamment d'informations / de code pour reproduire le problème. Il n'y a donc aucun moyen de répondre à cette question, il semble que vous leur disiez comment faire quelque chose qui n'a rien à voir avec leur problème.
Popeye
2
Je suppose qu'il leur a été beaucoup plus utile de simplement clore le problème. Je l'ai! +1 pour David G pour avoir réellement essayé d'aider quelqu'un sur StackOverflow. Je souhaite que je pourrais -1 vos votes rapprochés !!! Cela se produit toujours pour les utilisateurs et cela peut être un bogue dans iOS7 pour tout ce que nous savons. Donc, ce n'est pas parce que quelqu'un ne peut pas publier le code incriminé que la question n'est pas valide et utile pour les autres utilisateurs. Même si c'est juste pour voir que d'autres personnes voient le même problème sans aucune raison logique pour laquelle elles le voient. -rrh
Richie Hyatt