it-swarm.com.de

iOS-App-Fehler - Kann nicht als Unteransicht hinzugefügt werden

Ich habe diesen Absturzbericht erhalten, weiß aber nicht, wie ich ihn debuggen kann.

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

Die iOS-Version ist 7.0.3 . Jemand erlebt diesen komischen Absturz?

AKTUALISIEREN:

Ich weiß nicht, wo in meinem Code dieser Absturz verursacht wurde, daher kann ich den Code hier nicht posten, sorry.

Zweites UPDATE

Siehe die Antwort unten.

149
Arnol

Weitere Details zu diesem Absturz werde ich in meiner App beschreiben und als beantwortet markieren.

Meine App hat einen UINavigationController mit dem Root-Controller ist ein UITableViewController, der eine Liste von Notizobjekten enthält. Das Notizobjekt hat eine Inhaltseigenschaft in HTML. Wählen Sie eine Notiz, um zur Detailsteuerung zu gelangen.

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

Detailsteuerung

Dieser Controller verfügt über eine UIWebView, die den vom Root-Controller übergebenen Notizinhalt anzeigt.

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

Dieser Controller ist der Delegat des Webview-Steuerelements. Wenn die Notiz Links enthält, tippen Sie auf einen Link, um zum In-App-Webbrowser zu gelangen.

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

Ich habe täglich den oben genannten Absturzbericht erhalten. Ich weiß nicht, wo in meinem Code dieser Absturz verursacht wurde. Nach einigen Untersuchungen mit Hilfe eines Benutzers konnte ich diesen Absturz endlich beheben. Dieser HTML-Inhalt führt zum Absturz:

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

In der viewDidLoad-Methode des Detail-Controllers habe ich diese HTML-Datei in das Webview-Steuerelement geladen. Gleich danach wurde die obige Delegat-Methode sofort mit request.URL aufgerufen. Diese Delegatenmethode ruft die pushViewController-Methode auf, während sie sich in viewDidLoad => crash befindet!

Ich habe diesen Absturz behoben, indem ich den navigationType überprüfte:

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

Hoffe das hilft

8
Arnol

Ich spekuliere auf etwas Ähnlichem, das ich kürzlich debuggt habe ....__ Wenn Sie einen Ansichtscontroller mit Animated: YES Push (oder Pop) ausführen, wird dies nicht sofort abgeschlossen, und schlechte Dinge passieren, wenn Sie ein anderes Push oder Pop ausführen bevor die Animation abgeschlossen ist. Sie können leicht testen, ob dies tatsächlich der Fall ist, indem Sie Ihre Push- und Pop-Vorgänge vorübergehend in Animated: NO ändern (damit sie synchron ausgeführt werden) und sehen, ob dies den Absturz beseitigt. Wenn dies tatsächlich Ihr Problem ist und Sie dies möchten, können Sie dies tun Aktivieren Sie die Animation wieder, dann ist die korrekte Strategie die Implementierung des UINavigationControllerDelegate-Protokolls .. _. Dazu gehört die folgende Methode, die nach Abschluss der Animation aufgerufen wird:

navigationController:didShowViewController:animated:

Grundsätzlich möchten Sie etwas Code nach Bedarf in diese Methode verschieben, um sicherzustellen, dass keine anderen Aktionen ausgeführt werden, die eine Änderung des NavigationController-Stapels bewirken könnten, bis die Animation abgeschlossen ist und der Stapel für weitere Änderungen bereit ist.

48
RobP

Wir haben auch angefangen, dieses Problem zu bekommen, und es war sehr wahrscheinlich, dass unseres durch dasselbe Problem verursacht wurde.

In unserem Fall mussten wir in einigen Fällen Daten aus dem Backend abrufen, was bedeutete, dass ein Benutzer etwas antippen konnte, und es gab eine kurze Verzögerung, bevor der Nav-Push auftrat. Wenn ein Benutzer schnell herumtippt, erhält er möglicherweise zwei Nav-Pushs vom selben View-Controller, was genau diese Ausnahme auslöst.

Unsere Lösung ist eine Kategorie auf dem UINavigationController, die Pushs/​​Pops verhindert, es sei denn, der oberste vc ist zu einem bestimmten Zeitpunkt derselbe.

.h Datei:

@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

.m Datei:

@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

Bisher scheint dies das Problem für uns gelöst zu haben. Beispiel:

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

Grundsätzlich gilt: Vor nicht benutzerbezogenen Verzögerungen Ziehen Sie eine Sperre vom entsprechenden Nav-Controller und fügen Sie sie in den Aufruf von Push/Pop ein.

Das Wort "sperren" kann eine etwas schlechte Formulierung sein, da es andeutet, dass es eine Art Sperren gibt, das entriegelt werden muss, aber da es nirgendwo eine "entsperren" -Methode gibt, ist es wahrscheinlich in Ordnung.

(Als Nebenbemerkung sind "nicht benutzerbezogene Verzögerungen" Verzögerungen, die der Code verursacht, dh alles asynchron. Benutzer, die auf einen animierten Nav-Controller tippen, werden nicht gezählt, und die navigationLock: -Version muss nicht verwendet werden Fälle.)

13
Kalle

Dieser Code behebt das Problem: https://Gist.github.com/nonamelive/9334458

Es verwendet eine private API, aber ich kann bestätigen, dass es im App Store sicher ist. (Eine meiner Apps, die diesen Code verwenden, wurde vom App Store genehmigt.)

@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;
}
12
nonamelive

Ich hatte das gleiche Problem. Was für mich einfach funktioniert hat, ist das Ändern von Animated: Ja zu Animated: Nein.

Es sieht so aus, als ob das Problem daran lag, dass die Animation nicht rechtzeitig fertiggestellt wurde.

Hoffe das hilft jemandem.

5
Lion789

Um diesen Fehler zu reproduzieren, versuchen Sie, gleichzeitig zwei Ansichts-Controller zu drücken. Oder gleichzeitig drücken und knallen. Beispiel:

enter image description here Ich habe eine Kategorie erstellt, die diese Anrufe abfängt und sicher macht, indem sichergestellt wird, dass keine anderen Pushs ausgeführt werden, während einer ausgeführt wird. Kopieren Sie einfach den Code in Ihr Projekt, und dank des Methoden-Swizzling können Sie gut arbeiten.

#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
3
dan

Entschuldigen Sie, dass Sie zu spät zur Party sind. Ich hatte kürzlich dieses Problem, bei dem meine Navigationsleiste in den beschädigten Zustand wechselt, weil mehr als ein Ansichtscontroller gleichzeitig gedrückt wird. Dies geschieht, weil der andere View-Controller gedrückt wird, während der erste View-Controller noch animiert wird. Ich nehme den Hinweis aus der nichtameliven Antwort und habe eine einfache Lösung gefunden, die in meinem Fall funktioniert. Sie müssen lediglich die Variable UINavigationController subclassieren, die pushViewController-Methode überschreiben und prüfen, ob die Animation des vorherigen View-Controllers noch abgeschlossen ist. Sie können den Abschluss der Animation abhören, indem Sie Ihrer Klasse einen Delegaten von UINavigationControllerDelegate zuweisen und den Delegaten auf self setzen.

Ich habe ein Gist hier hochgeladen, um die Sache zu vereinfachen.

Stellen Sie einfach sicher, dass Sie diese neue Klasse in Ihrem Storyboard als NavigationController festgelegt haben.

1
nikhil.thakkar

Ich denke, dass Push/Popping-View-Controller mit Animationen an jedem Punkt vollkommen in Ordnung sein sollten und das SDK die Warteschlange der Aufrufe für uns angenehm behandeln sollte.

Daher ist dies nicht der Fall, und alle Lösungen versuchen, nachfolgende Pushs zu ignorieren, was als Fehler angesehen werden kann, da der endgültige Navigationsstapel nicht dem beabsichtigten Code entspricht.

Ich habe stattdessen eine Warteschlange für Push-Anrufe implementiert:

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

Es behandelt keine gemischten Warteschlangen von Push und Pops, aber es ist ein guter Starter, um die meisten Abstürze zu beheben.

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

1
Rivera

Ich habe auch dieses Problem erlebt. Lass mich dir meinen Code zeigen: 

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

Der Fehler tritt aufgrund dieser Zeile auf: 

firstView.addSubView(firstView)

Sie können sich nicht selbst zur Unteransicht hinzufügen. Ich habe die Codezeile folgendermaßen geändert: 

firstView.addSubView(secondView)

Der Fehler ging weg und ich konnte beide Ansichten sehen. Ich dachte nur, das würde jedem helfen, der ein Beispiel sehen wollte.

1
halapgos1

Suchen Sie in Ihrem Code nach "addSubview".

An einer der Stellen, an denen Sie diese Methode aufgerufen haben, haben Sie versucht, mit dieser Methode eine Ansicht zu einem eigenen Sub-Views-Array hinzuzufügen.

Zum Beispiel:

[self.view addSubview:self.view];

Oder:

[self.myLabel addSubview:self.myLabel];
1
Michal Shatz

nonamelives Lösung ist großartig. Wenn Sie jedoch nicht die private API verwenden möchten, können Sie einfach die UINavigationControllerDelegate-Methode verwenden. Oder Sie können die animierte YES in NO..__ ändern es ist hilfreich : )

https://github.com/antrix1989/ANNavigationController

0
NSKevin

Basierend auf @RobP großer Hinweis habe ich UINavigationController-Unterklasse gemacht, um solche Probleme zu vermeiden. Es erledigt das Schieben und/oder Poppen und Sie können sicher Folgendes ausführen:

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

Wenn das Flag "acceptConflictingCommands" aktiviert ist (standardmäßig), sieht der Benutzer animiertes Pushing von vc1, vc2, vc3 und dann ein animiertes Poppen von vc3. Wenn 'acceptConflictingCommands' den Wert false hat, werden alle Push/Pop-Anforderungen verworfen, bis vc1 vollständig gepusht ist. Daher werden die anderen 3 Aufrufe verworfen.

0
hris.to

Manchmal haben Sie versehentlich versucht, eine Ansicht zu seiner eigenen Ansicht hinzuzufügen.

halfView.addSubview(halfView)

Ändern Sie dies in Ihre Unteransicht.

halfView.addSubview(favView)
0
Vinoth Vino

Ich hatte dieses Problem häufig gesucht, es wurden möglicherweise zwei oder mehr VC gleichzeitig gedrückt, was das Pushing-Animationsproblem verursacht. Sie können sich darauf beziehen: Kann sich nicht als Unteransicht hinzufügen 崩溃 解决办法

stellen Sie nur sicher, dass es einen VC gibt, der sich gleichzeitig im Übergang befindet - viel Glück.

0
FFur