165 votes

Erreur de l'application iOS - Impossible d'ajouter self comme sous-vue

J'ai reçu ce rapport de panne, 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 fait l'expérience de ce crash bizarre ?

UPDATE :

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

Deuxième UPDATE

Voir la réponse ci-dessous.

3 votes

Pouvez-vous nous montrer votre code ?

43 votes

Désolé mais je ne comprends pas votre réaction excessive. L'erreur de pile est claire sur le problème. Donc premièrement, vous pouvez laisser l'utilisateur mettre plus de code comme on lui a demandé (seulement 1h la question posée et vous demandez de la fermer immédiatement). Deuxièmement, j'ai reçu un downvote sans raison puisque ma réponse est claire. La question est "Quelqu'un a t-il fait l'expérience de ce crash bizarre ?". Et j'ai dit pourquoi il a eu ça. Même si ce n'est pas spécifiquement situé dans son code.

0 votes

@AncAinu 1) Cette question est parfaite pour être fermée pour la raison donnée, elle peut être rouverte quand ils mettent à jour leur question avec le code correct. 2) Je n'ai rien à voir avec votre vote négatif, mais comme il y a actuellement plus de votes en faveur de la fermeture, cela signifie qu'il y a plus d'utilisateurs qui sont d'accord avec moi. Et si la question est "Quelqu'un a-t-il fait l'expérience de ce crash bizarre ?", une réponse très simple de OUI peut être mis, les questions de ce type ne sont pas autorisées et seront supprimées. Ce n'est pas comme ça que fonctionne stackoverflow. S'ils ne sont pas prêts à partager leur code, ils doivent être prêts à le faire fermer. SIMPLE

54voto

RobP Points 1817

Je spécule en me basant sur quelque chose de similaire que j'ai débogué récemment... si vous poussez (ou pop) un contrôleur de vue avec Animated:YES, il ne se termine pas tout de suite, et de mauvaises choses se produisent si vous faites un autre push ou pop avant que l'animation ne se termine. Vous pouvez facilement tester si c'est effectivement le cas en changeant 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 bonne stratégie consiste à mettre en œuvre le protocole UINavigationControllerDelegate. Cela inclut la méthode suivante, qui est appelée une fois l'animation terminée :

navigationController:didShowViewController:animated:

En fait, il s'agit de transférer du code dans cette méthode, si nécessaire, afin de s'assurer qu'aucune autre action susceptible de modifier la pile NavigationController ne se produira jusqu'à ce que l'animation soit terminée et que la pile soit prête à recevoir d'autres modifications.

0 votes

Vers iOS 4 et quelque, j'ai vu quelque chose de similaire dans l'une de nos applications. Il semble que le code de l'interface utilisateur était gravement perturbé si l'on faisait apparaître une animation puis qu'on la poussait immédiatement. J'ai fini par changer le code pour ne jamais faire deux opérations push/pop animées l'une après l'autre. 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'existe pas encore.

0 votes

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'interface utilisateur du nouveau contrôleur de vue. [newViewController setLabelTitle:...] 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.

0 votes

Content que ça ait aidé ! Il est intéressant de noter que le déplacement du code vers le nouveau ViewController est également une option si vous savez de quelle classe il s'agit. Je trouve de plus en plus utile d'attraper les différentes méthodes du protocole UINavigationControllerDelegate, de toute façon. Et j'ai découvert que dans iOS8, les événements se déclenchent dans des ordres différents, et que certaines choses qui étaient plus ou moins synchrones reviennent maintenant rapidement mais programment des choses à faire en arrière-plan de manière asynchrone, ce qui crée beaucoup de nouveaux bugs de synchronisation comme ceux-ci. Merci, Apple !

14voto

Kalle Points 6804

Nous avons commencé à avoir ce problème également, et il y a de fortes chances que les nôtres soient causés par le même problème.

Dans notre cas, nous devions parfois extraire des données de l'arrière-plan, ce qui signifiait qu'un utilisateur pouvait toucher quelque chose et qu'il y avait un léger délai avant que la navigation ne soit lancée. Si un utilisateur tapote rapidement, il peut se retrouver avec deux poussées de navigation à partir du même contrôleur de vue, ce qui déclenche cette exception.

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

.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

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

En gros, la règle est la suivante : avant toute retards non liés à l'utilisateur saisir un verrou dans le contrôleur naval concerné et l'inclure dans l'appel à push/pop.

Le mot "verrou" est peut-être mal formulé, car il peut laisser entendre qu'il y a une sorte de verrou à déverrouiller, mais comme il n'y a pas de méthode de "déverrouillage", c'est probablement correct.

(En passant, les "retards non liés à l'utilisateur" sont tous les retards causés par le code, 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 la version navigationLock : pour ces cas).

0 votes

Puisque vous avez dit que vous essayiez cette solution, a-t-elle résolu le problème pour vous ?

0 votes

Jusqu'à présent, oui. Le problème n'a pas refait surface. Je vais mettre à jour la réponse.

4 votes

J'ai utilisé une version modifiée basée sur la vôtre : gist.github.com/mdewolfe/9369751 . On dirait qu'il l'a réparé.

12voto

nonamelive Points 1960

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

Il utilise une API privée, mais je peux confirmer qu'il est sécurisé par l'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;
}

0 votes

C'est la meilleure solution jusqu'à présent, avec d'autres solutions, j'obtiendrais encore aléatoirement le problème de la double pression ou j'obtiendrais un contrôleur de navigation gelé.

0 votes

Ce code ne compile pas pour moi, il manque quelque chose ?

8voto

Arnol Points 489

Je décrirai plus de détails sur ce crash dans mon application et marquerai cette question comme répondue.

Mon application comporte un UINavigationController dont le contrôleur racine est un UITableViewController qui contient une liste d'objets note. L'objet note a une propriété content en html. En sélectionnant une note, on passe 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 possède un UIWebView, qui 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, appuyer sur un lien permet d'accéder au navigateur Web de 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 d'accident ci-dessus tous les jours. Je ne sais pas où dans mon code a causé ce crash. Après quelques recherches et avec l'aide d'un utilisateur, j'ai finalement réussi à réparer ce crash. Le contenu html suivant provoque le crash :

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

Dans la méthode viewDidLoad du contrôleur de détail, j'ai chargé ce html dans le contrôle webview, juste après, la méthode delegate 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 alors qu'elle est en viewDidLoad => crash !

J'ai résolu ce problème en vérifiant le type de navigation :

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

J'espère que cela vous aidera

3 votes

Ne serait-ce pas une bonne option de pousser le contrôleur sans animation lorsqu'il est appelé à partir de viewDidLoad ?

6voto

Lion789 Points 660

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

Il semble que le problème soit dû au fait que l'animation ne se termine pas à temps.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X