iOS-App-Fehler - Selbst kann nicht als Unteransicht hinzugefügt werden

157

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

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. Hat jemand diesen seltsamen Absturz erlebt?

AKTUALISIEREN:

Ich weiß nicht, wo in meinem Code dieser Absturz verursacht wurde, daher kann ich den Code hier leider nicht veröffentlichen.

Zweites UPDATE

Siehe die Antwort unten.

Arnol
quelle
3
Können Sie uns Ihren Code zeigen?
David Gölzhäuser
43
Entschuldigung, aber ich verstehe Ihre Überreaktion nicht. Der Stapelfehler ist bei dem Problem klar. Sie können den Benutzer also zunächst mehr Code eingeben lassen, als ihm gestellt wurde (nur 1 Stunde nach der gestellten Frage, und Sie bitten, sie sofort zu schließen). Zweitens habe ich ohne Grund eine Ablehnung erhalten, da meine Antwort klar ist. Die Frage ist "Hat jemand diesen seltsamen Absturz erlebt?". Und ich erzählte, warum er das bekam. Auch wenn es nicht speziell in seinem Code enthalten ist.
Tancrede Chazallet
9
Diese Frage ist richtig. Der Benutzer kann in dieser Situation keinen genauen Fehlercode angeben. weil er nicht weiß, in welchem ​​View Controller etwas schief geht
Ravindra Bagale
16
Wir verwenden Crashlytics und haben über 30 Benutzer, die unsere App mit der Option "Selbst nicht als Unteransicht hinzufügen" abgestürzt sind. Natürlich haben wir keinen Code, der versucht, sich selbst als Unteransicht hinzuzufügen. Aus dem Backtrace gibt es überhaupt keinen Hinweis auf unsere App.
Richie Hyatt
49
Abstimmung zur Wiedereröffnung; Die Leute, die es schließen, machen anscheinend nicht viel iOS-Entwickler, da dies ein häufiges Problem ist, das von iOS7 eingeführt wurde und eine ganze Reihe von Apps tötet, die unter iOS6 in Ordnung waren (ich habe es bei mehreren Projekten von verschiedenen Unternehmen gesehen). Es ist schade, dass diese Frage bei Google ein Top-Hit ist, aber einige kurzsichtige Leute haben sie geschlossen.
Adam

Antworten:

51

Ich spekuliere basierend auf etwas Ähnlichem, das ich kürzlich getestet habe ... Wenn Sie einen Ansichts-Controller mit Animated: YES drücken (oder platzen lassen), wird er nicht sofort abgeschlossen, und schlimme Dinge passieren, wenn Sie vor der Animation einen weiteren Push oder Pop ausführen abgeschlossen. Sie können leicht testen, ob dies tatsächlich der Fall ist, indem Sie Ihre Push- und Pop-Vorgänge vorübergehend in Animiert: NEIN ändern (damit sie synchron abgeschlossen werden) und prüfen, ob der Absturz dadurch behoben wird. Wenn dies tatsächlich Ihr Problem ist und Sie die Animation wieder einschalten möchten, besteht die richtige Strategie darin, das UINavigationControllerDelegate-Protokoll zu implementieren. Dies umfasst die folgende Methode, die nach Abschluss der Animation aufgerufen wird:

navigationController:didShowViewController:animated:

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

RobP
quelle
Vor langer Zeit über iOS 4 - etwas, das ich in einer unserer Apps gesehen habe - IIRC. Wenn Sie animiert auftauchen und dann sofort animiert drücken, wird der UI-Code stark durcheinander gebracht. Am Ende wurde nur geändert, um niemals zwei animierte Push / Pop-Operationen hintereinander auszuführen. Natürlich wurde die gesamte zugrunde liegende Logik seitdem neu geschrieben, aber es ist nicht schwer zu glauben, dass ein ähnlicher Fehler noch nicht vorhanden ist.
Hot Licks
Ich hatte das gleiche Problem. In meinem Fall geschah dies, weil die App eine Anweisung ausführte, die [newViewController setLabelTitle:...]die Benutzeroberfläche des neuen View Controllers unmittelbar nach dem Aufruf von pushViewController mit änderte. Animated:YES.Und ich löste das Verschieben der setLabelTitle-Methode nach viewDidLoad auf dem newViewController. Danke, dass du mir den Hinweis gegeben hast.
Jeprubio
Ich bin froh, dass das geholfen hat! Ein guter Punkt, dass das Verschieben von Code in den neuen ViewController auch eine Option ist, wenn Sie wissen, um welche Klasse es sich handelt. Immer mehr finde ich es auf jeden Fall nützlich, die verschiedenen Methoden des UINavigationControllerDelegate-Protokolls zu erfassen. Und ich habe festgestellt, dass in iOS8-Ereignissen Ereignisse in unterschiedlicher Reihenfolge ausgelöst werden und einige Dinge, die früher mehr oder weniger synchron waren, jetzt schnell zurückkehren, aber Dinge so planen, dass sie asynchron im Hintergrund ausgeführt werden, wodurch viele neue Timing-Fehler wie diese entstehen. Danke, Apple!
RobP
14

Wir bekamen auch dieses Problem, und es war sehr wahrscheinlich, dass unser Problem durch dasselbe Problem verursacht wurde.

In unserem Fall mussten wir in einigen Fällen Daten aus dem Back-End abrufen, was bedeutete, dass ein Benutzer möglicherweise auf etwas tippte und es dann eine leichte Verzögerung gab, bevor der Navigations-Push auftrat. Wenn ein Benutzer schnell herum tippte, wurden möglicherweise zwei Navigationsschübe vom selben Ansichts-Controller ausgeführt, was genau diese Ausnahme auslöste.

Unsere Lösung ist eine Kategorie im UINavigationController, die Pushs / ​​Pops verhindert, es sei denn, die oberste VC ist zu einem bestimmten Zeitpunkt dieselbe.

.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 die Regel: Bevor nicht benutzerbezogene Verzögerungen auftreten , nehmen Sie eine Sperre vom entsprechenden Navigationscontroller und nehmen Sie sie in den Aufruf zum Drücken / Pop auf.

Das Wort "Sperre" ist möglicherweise etwas schlecht formuliert, da es möglicherweise darauf hindeutet, dass eine Art Sperre stattfindet, die entsperrt werden muss. Da es jedoch nirgendwo eine "Entsperr" -Methode gibt, ist dies wahrscheinlich in Ordnung.

(Nebenbei bemerkt, "nicht benutzerbezogene Verzögerungen" sind alle Verzögerungen, die der Code verursacht, dh alles, was asynchron ist. Benutzer, die auf einen Navigationscontroller tippen, der animiert gedrückt wird, zählen nicht und es ist nicht erforderlich, die navigationLock: -Version für diese auszuführen Fälle.)

Kalle
quelle
Hat das Problem für Sie gelöst, seit Sie sagten, Sie hätten diese Lösung ausprobiert?
Mike D
So weit ja. Das Problem ist nicht wieder aufgetreten. Ich werde die Antwort aktualisieren.
Kalle
4
Ich habe eine modifizierte Version verwendet, die auf Ihrer basiert: gist.github.com/mdewolfe/9369751 . Sieht so aus, als hätte es das Problem behoben.
Mike D
2
@Kalle Diese Lösung funktioniert für Push / Pop. Aber wie kann ich diesen Fehler beheben, wenn ich segue verwende?
Geek
@ Kadle Kannst du mir helfen, dies umzusetzen? Schauen Sie stackoverflow.com/q/23247713/1323014 THX
Marckaraujo
12

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

Es verwendet eine private API, aber ich kann bestätigen, dass es 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;
}
nichtschmelzend
quelle
Dies war bisher die beste Lösung, bei einigen anderen würde ich entweder immer noch zufällig das Double-Push-Problem bekommen oder ich würde einen eingefrorenen Navigations-Controller bekommen.
Blueice
Dieser Code wird für mich nicht kompiliert, fehlt etwas?
Maxime B
8

Ich werde weitere Details zu diesem Absturz in meiner App beschreiben und diese als beantwortet markieren.

Meine App verfügt über einen UINavigationController, wobei der Root-Controller ein UITableViewController ist, der eine Liste von Notizobjekten enthält. Das Notizobjekt hat eine Inhaltseigenschaft in HTML. Wählen Sie eine Notiz aus, um zum Detail-Controller zu gelangen.

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

Detail-Controller

Dieser Controller verfügt über eine UIWebView, in der der vom Root-Controller übergebene Noteninhalt angezeigt wird.

- (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 jeden Tag den obigen Absturzbericht erhalten. Ich weiß nicht, wo in meinem Code dieser Absturz verursacht wurde. Nach einigen Nachforschungen mit Hilfe eines Benutzers konnte ich diesen Absturz endlich beheben. Dieser HTML-Inhalt verursacht den Absturz:

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

In der viewDidLoad-Methode des Detail-Controllers habe ich diesen HTML-Code in das Webview-Steuerelement geladen. Unmittelbar danach wurde die oben genannte Delegate-Methode sofort mit request aufgerufen. URL ist die Quelle des Iframes (google.com). Diese Delegate-Methode ruft die pushViewController-Methode auf, während in viewDidLoad => Absturz!

Ich habe diesen Absturz behoben, indem ich den Navigationstyp überprüft habe:

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

Hoffe das hilft

Arnol
quelle
1
Wäre es nicht eine gute Option, den Controller ohne Animation zu pushen, wenn er von aufgerufen wird viewDidLoad?
Rivera
6

Ich hatte das gleiche Problem. Was für mich einfach funktionierte, war das Ändern von Animiert: Ja in Animiert: Nein.

Es sieht so aus, als ob das Problem darauf zurückzuführen ist, dass die Animation nicht rechtzeitig abgeschlossen wurde.

Hoffe das hilft jemandem.

Lion789
quelle
3

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

Geben Sie hier die Bildbeschreibung ein Ich habe eine Kategorie erstellt, die diese Anrufe abfängt und sicher macht, indem sichergestellt wird, dass keine weiteren Pushs ausgeführt werden, während einer ausgeführt wird. Kopieren Sie einfach den Code in Ihr Projekt und schon können Sie loslegen.

#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
quelle
Ein Problem bei dieser Lösung besteht darin, dass, wenn Sie anrufen popToRootViewControlleroder popToViewController:wenn Sie sich bereits auf dem Root-View-Controller oder auf dem ViewController befinden, dieser didShowViewControllernicht aufgerufen wird und er nicht mehr funktioniert viewTransitionInProgress.
Divergio
1
Können Sie diese Zeilen erklären: self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController; [self.interactivePopGestureRecognizer setEnabled:YES]; Wann wurde der Erkenner deaktiviert? Und woher wissen Sie, was der Delegierte sein sollte? Mit diesen Zeilen bricht es für mich die Pop-Geste, nachdem ich einmal aufgetaucht bin.
Divergio
Ich habe versucht, dies zu implementieren, und nach einer Weile blockiert es den Navigationscontroller, wahrscheinlich aufgrund dessen, was @divergio erwähnt hat.
Blueice
2

Ich habe gerade dieses Problem erlebt. Lassen Sie mich Ihnen 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 zur Unteransicht hinzufügen. Ich habe die Codezeile geändert in:

firstView.addSubView(secondView)

Der Fehler verschwand und ich konnte beide Ansichten sehen. Ich dachte nur, das würde jedem helfen, der ein Beispiel sehen möchte.

halapgos1
quelle
Ich habe auch diesen Ansatz ausprobiert, aber der Stacktrace wäre anders und würde tatsächlich die Zeile Ihres Codes anzeigen, die den Absturz verursacht. Ich glaube, das Quellproblem unterscheidet sich von der Frage.
Ben
1

Suchen Sie Ihren Code nach "addSubview".

An einer der Stellen, an denen Sie diese Methode aufgerufen haben, haben Sie versucht, mit dieser Methode eine Ansicht zu ihrem eigenen Array für Unteransichten hinzuzufügen.

Beispielsweise:

[self.view addSubview:self.view];

Oder:

[self.myLabel addSubview:self.myLabel];
Michal Shatz
quelle
Freut mich zu hören, dass Sie Ihren Fehler gefunden haben, und jetzt verstehe ich genau, warum Sie die Meldung "Ich kann mich nicht als Unteransicht hinzufügen" erhalten haben. An einem Punkt, an dem Ihr View2 der Root-View-Controller Ihres Navigations-Controllers war, haben Sie View2 gedrückt, was dies verursachte: Sie [View2.view addSubview:View2.view]haben sich selbst als Unteransicht hinzugefügt.
Michal Shatz
1

Ich denke, dass das Push / Popping von View-Controllern mit Animation zu jedem Zeitpunkt vollkommen in Ordnung sein sollte und das SDK die Warteschlange der Anrufe für uns freundlich 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 Code entspricht.

Ich habe stattdessen eine Push-Call-Warteschlange 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 Anfang, um die meisten unserer Abstürze zu beheben.

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

Rivera
quelle
Ich habe Ihre Lösung ausprobiert, die sehr gut zu sein scheint, aber ich habe ein Problem. Wenn ich 2 View Controller mit animiertem NO nacheinander schiebe, sehe ich ganz kurz den ersten. Das ist vorher nicht passiert. Irgendeine Idee, was ich tun kann, um das Problem zu beheben?
Jan
Ich versuche, ein Projekt zu erstellen, das durchweg diese Art von Absturz verursachen kann (mein reales Projekt erhält Absturzberichte wie diesen). Ich habe eine einfache App mit einem Navigations-Controller, einem Root-Controller und einer Schaltfläche erstellt, die sofort 4 neue Ansichts-Controller auf den Navigationsstapel schiebt und dann den letzten herausspringt. Ohne spezielle Unterklassen oder irgendetwas scheint es tatsächlich gut zu funktionieren. Hat Apple dies kürzlich behoben?
Cruinh
1

Tut mir leid, dass ich zu spät zur Party komme. Ich hatte kürzlich dieses Problem, bei dem meine Navigationsleiste beschädigt wurde, weil mehr als ein Ansichts-Controller gleichzeitig gedrückt wurde. Dies liegt daran, dass der andere Ansichts-Controller gedrückt wird, während der erste Ansichts-Controller noch animiert. Ausgehend von der nicht-formelhaften Antwort habe ich meine einfache Lösung gefunden, die in meinem Fall funktioniert. Sie müssen nur UINavigationControllerdie pushViewController-Methode unterordnen und überschreiben und prüfen, ob die vorherige View Controller-Animation noch abgeschlossen ist. Sie können den Abschluss der Animation anhören, indem Sie Ihre Klasse zu einem Delegierten machen UINavigationControllerDelegateund den Delegierten auf festlegenself .

Ich habe hier einen Kern hochgeladen , um die Dinge zu .

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

nikhil.thakkar
quelle
Bisher scheint es die Abstürze in der App behoben zu haben, an der ich gearbeitet habe ... Außerdem ist die Lösung ziemlich einfach und klar: Die erste Viewcontroller-Animation war noch nicht vollständig. Leute, die das gleiche Problem haben, sollten dies überprüfen.
Alasker
0

Basierend auf dem großartigen Hinweis von @RobP habe ich die Unterklasse UINavigationController erstellt , um solche Probleme zu vermeiden. Es handhabt das Schieben und / oder Knallen und Sie können sicher 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' auf true gesetzt ist (standardmäßig), wird dem Benutzer das animierte Pushing von vc1, vc2, vc3 und anschließend das animierte Poppen von vc3 angezeigt. Wenn 'acceptConflictingCommands' falsch ist, werden alle Push / Pop-Anforderungen verworfen, bis vc1 vollständig gepusht ist - daher werden andere 3 Aufrufe verworfen.

hris.to.
quelle
Widersprechen sich diese Befehle tatsächlich? Ich habe gerade ein schnelles neues Projekt zusammengestellt, um zu sehen, wie dieser Absturz passiert, und dabei Code wie oben (aber in Swift) verwendet. Dabei wurde tatsächlich jeder Push und der Pop nacheinander ausgeführt. einer nach dem anderen. Kein Unfall. Ohne Verwendung von Unterklassen. Nur der reguläre UINavigationController von Apple.
Cruinh
Es stürzte tatsächlich mit ObjC und iOS 7 ab. Ich kann nicht bestätigen, ob es jetzt noch auftritt. Sind Sie sicher, dass Sie die Befehle mit animated:trueflag ausführen ?
Bis zum
Ja, ich habe die animierte: true-Flagge verwendet.
Cruinh
0

Die Lösung von nonamelive ist fantastisch. Wenn Sie die private API jedoch nicht verwenden möchten, können Sie einfach die UINavigationControllerDelegateMethode ausführen. Oder Sie können die Animation YESin ändern NO. Hier ist ein Beispiel für Code, den Sie erben können. Hoffe es ist hilfreich :)

https://github.com/antrix1989/ANNavigationController

NSKevin
quelle
0

Manchmal haben Sie fälschlicherweise versucht, eine Ansicht zu einer eigenen Ansicht hinzuzufügen.

halfView.addSubview(halfView)

Ändern Sie dies in Ihre Unteransicht.

halfView.addSubview(favView)
Vinoth Vino
quelle
0

Ich bin auch auf dieses Problem gestoßen. Bei der Analyse des Firebase-Protokolls stellte ich fest, dass dieses Problem nur beim Kaltstart der App auftritt. Also habe ich eine Demo geschrieben , die diesen Absturz reproduzieren kann.

.

Ich habe auch festgestellt, dass das Ausführen mehrerer Pushs nicht wieder dasselbe Problem verursacht, wenn der Root-Viewcontroller des Fensters angezeigt wird. (Sie können testColdStartUp (rootNav) in AppDelegate.swift kommentieren und den Kommentar testColdStartUp () in ViewController.swift auskommentieren.)

ps: Ich habe die Szene dieses Absturzes in meiner App analysiert. Wenn der Benutzer auf die Push-Benachrichtigung klickt, um die App kalt zu starten, befindet sich die App immer noch auf der Startseite und klickt auf einen weiteren Push, um zu springen. Zu diesem Zeitpunkt wird in der App möglicherweise der Absturz angezeigt. Meine aktuelle Lösung besteht darin, den Push- oder Universal Link-Kaltstart zwischenzuspeichern, um die App-Jump-Seite zu öffnen, auf die Anzeige des Rootview-Controllers zu warten und dann die Ausführung zu verzögern.

Jader Yang
quelle
-2

Versuchen Sie Ihre Navigation mit der Verzögerungsmethode, um die letzte Navigationsanimation zu vervollständigen.

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

Naeem Paracha
quelle
-2

Eine Ansicht kann nicht selbst als Unteransicht hinzugefügt werden.

Die Ansichten behalten eine Eltern-Kind-Hierarchie bei. Wenn Sie also eine Ansicht als Unteransicht an sich hinzufügen, wird dies durch eine Ausnahme geschehen.

Wenn eine Klasse UIViewController ist, verwenden Sie self.view, um ihre Ansicht zu erhalten.

Wenn eine Klasse eine UIView-Klasse ist, verwenden Sie self, um ihre Ansicht zu erhalten.

Mradul Kumar
quelle
-3

Sie können sich nicht als Unteransicht hinzufügen, wenn es sich um eine UiViewController-Klasse handelt. Sie können sich selbst als Unteransicht hinzufügen, wenn es sich um eine UiView-Klasse handelt.

user1533983
quelle
-9

Wenn Sie einer Ansicht eine Unteransicht hinzufügen möchten, können Sie dies folgendermaßen tun.

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
quelle
Gut gemacht, es ist ein schönes Stück Code. Können Sie mir jetzt sagen, was dies mit dieser Frage zu tun hat und wie sie gelöst wird?
Popeye
@ Popeye Hast du eine bessere Idee?
David Gölzhäuser
Nein, da nicht genügend Informationen / Code bereitgestellt wurden, um das Problem zu replizieren. Es gibt also keine Möglichkeit, dies zu beantworten. Es sieht nur so aus, als würden Sie ihnen sagen, wie sie etwas tun sollen, das nichts mit ihrem Problem zu tun hat.
Popeye
2
Ich denke, es war viel hilfreicher für sie, das Problem einfach zu schließen. Verstanden! +1 für David G, weil er tatsächlich versucht hat, jemandem auf StackOverflow zu helfen. Ich wünschte, ich könnte -1 Ihre engen Stimmen !!! Dies geschieht immer noch für Benutzer und es kann nach allem, was wir wissen, ein Fehler in iOS7 sein. Nur weil jemand den beleidigenden Code nicht posten kann, heißt das nicht, dass die Frage für andere Benutzer nicht gültig und wertvoll ist. Auch wenn es nur darum geht zu sehen, dass andere Menschen das gleiche Problem ohne logischen Grund sehen, warum sie es sehen. -rrh
Richie Hyatt