iOS app error - Can't add self as subview
Asked Answered
L

19

165

I received this crash report, but I don't know how to debug it.

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

The iOS version is 7.0.3. Anyone experience this weird crash?

UPDATE:

I don't know where in my code caused this crash, so I can not post the code here, sorry.

Second UPDATE

See the answer below.

Luca answered 24/10, 2013 at 7:59 Comment(14)
Can you show us your code?Laing
Sorry but I don't understand your overreaction. The stack error is clear on the problem. So first, you can let the user put more code as asked to him (only 1h the question asked and you ask to close it immediately). Second I received a downvote for no reason since my answer is clear. The question is "Anyone experience this weird crash?". And I told why he got this. Even if it's not specifically located in its code.Filagree
@AncAinu 1) This question is perfect for being closed for the reason given, it can be reopened when they update their question with correct code. 2) I have nothing to do with your downvote but as there are currently more votes for closing this says to me there are more users that agree with me. And if the questions is "Anyone experience this weird crash?" than a very simple answer of YES can be put, question of such case aren't allowed and will be removed. This isn't how stackoverflow works. If they aren't prepared to share their code they need to be prepared to have it closed SIMPLEDisarmament
This question is one correct . user cant give exact code of error in this situation. because he dont know in which view controller something going wrongWakayama
We use Crashlytics and have over 30 users who have crashed our app with the "Can't add self as subview" of course we don't have code that tries to add itself as a subview. From the backtrace there is no reference to our app at all.Noumenon
The only place in my code I could find self added as a subview was SVSegmentedControl. I am also using SWRevealViewController though, and I wonder if this error has anything to do with that?Kazak
Voting to reopen; the people closing it don't do much iOS dev, apparently, since this is a common problem introduced by iOS7 and killing a whole bunch of apps that were fine on iOS6 (I've seen it on multiple projects from different companies). It's a pity that this question is a top-hit on Google, but a few short-sighted people closed it.Kith
Agreed we saw this with end user testing as well. Why close an issue when it is clearly described and it is an actual problem?Ombudsman
It must be iOS doing the adding of the subview. Perhaps there is code in viewDidLoad that could be moved to somewhere later in the life cycle. Perhaps viewWillLayoutSubviews?Atony
Arnol (or anyone else): Have you figured out the most effective way to prevent the problem?Scythe
I also am seeing this. Nor can I find any place in our code where we push a viewcontroller from viewDidLoad.Anomie
I got a similar problem crash report but can't figure out why. #21989912Divulgate
I got a similar problem and I also can't figure out how to reproduce it. All i got are logs that don't point to anything that can be useful. :(Recife
I also see this problem and have no idea on how to reproduce it. No hint of my own code in the stack trace....?!? any ideas on how to debug/reproduce it?Selectman
L
8

I will describe more details about this crash in my app and mark this as answered.

My app has a UINavigationController with the root controller is a UITableViewController that contains a list of note objects. The note object has a content property in html. Select a note will go to the detail controller.

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

Detail controller

This controller has a UIWebView, display the note content passed from the root controller.

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

This controller is the delegate of the webview control. If the note contains links, tap a link will go to the in-app web browser.

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

I received the above crash report everyday. I don't know where in my code caused this crash. After some investigates with the help of a user, I was finally able to fix this crash. This html content will cause the crash:

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

In the viewDidLoad method of the detail controller, I loaded this html to the webview control, right after that, the above delegate method was called immediately with request.URL is the iframe's source (google.com). This delegate method calls pushViewController method while in viewDidLoad => crash!

I fixed this crash by checking the navigationType:

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

Hope this helps

Luca answered 19/2, 2014 at 8:55 Comment(1)
Wouldn't it be a good option to push the controller without animation when called from viewDidLoad?Clearcut
A
54

I am speculating based on something similar that I debugged recently... if you push (or pop) a view controller with Animated:YES it doesn't complete right away, and bad things happen if you do another push or pop before the animation completes. You can easily test whether this is indeed the case by temporarily changing your Push and Pop operations to Animated:NO (so that they complete synchronously) and seeing if that eliminates the crash. If this is indeed your problem and you wish to turn animation back ON, then the correct strategy is to implement the UINavigationControllerDelegate protocol. This includes the following method, which is called after the animation is complete:

navigationController:didShowViewController:animated:

Basically you want to move some code as needed into this method to ensure that no other actions that could cause a change to the NavigationController stack will occur until the animation is finished and the stack is ready for more changes.

Aconcagua answered 20/1, 2014 at 5:26 Comment(3)
Way back about iOS 4-something I saw something similar in one of our apps -- IIRC, if you popped animated and then immediately pushed animated the UI code would get badly mucked up. Ended up just changing to never do two animated push/pop operations back to back. Of course, all the underlying logic has been rewritten since then, but it's not hard to believe that a similar bug isn't still there.Scythe
I had the same issue. In my case this happened because the app executed an instruction that changes the new view controller's ui [newViewController setLabelTitle:...] just after calling pushViewController with Animated:YES. And I solved moving the setLabelTitle method to viewDidLoad on the newViewController. Thanks for giving me the clue.Sadi
Glad that helped! Good point that moving code to the new ViewController is also an option if you know which class it will be. More and more I find it useful to catch the various methods of the UINavigationControllerDelegate protocol, in any case. And I've found that in iOS8 events fire in different orders, and some things that used to be more or less synchronous now return fast but schedule things to get done on background asynchronously, creating lots of new timing bugs like these. Thanks, Apple!Aconcagua
W
14

We started getting this issue as well, and chances were highly likely that ours were caused by the same problem.

In our case, we had to pull data from the back end in some cases, which meant a user might tap something and then there'd be a slight delay before the nav push occurred. If a user was rapidly tapping around, they might end up with two nav pushes from the same view controller, which triggered this very exception.

Our solution is a category on the UINavigationController which prevents pushes/pops unless the top vc is the same one from a given point in time.

.h file:

@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 file:

@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

So far this seems to have resolved the problem for us. Example:

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

Basically, the rule is: before any non user related delays grab a lock from the relevant nav controller, and include it in the call to push/pop.

The word "lock" may be slightly poor wording as it may insinuate there's some form of lock happening that needs unlocking, but since there's no "unlock" method anywhere, it's probably okay.

(As a sidenote, "non user related delays" are any delays that the code is causing, i.e. anything asynchronous. Users tapping on a nav controller which is animatedly pushed doesn't count and there's no need to do the navigationLock: version for those cases.)

Wolverhampton answered 31/1, 2014 at 16:42 Comment(5)
Since you said you were trying this solution out, has it solved the issue for you?Molecule
So far, yes. The problem has not resurfaced. I'll update the answer.Wolverhampton
I used a modified version based on yours: gist.github.com/mdewolfe/9369751. Looks like it has fixed it.Molecule
@Wolverhampton This solution works for push/pop. But how to solve this error if I use segue?Rudman
@Kadle Can you help me to implement this? Look stackoverflow.com/q/23247713/1323014 THXWigfall
M
12

This code resolves the issue: https://gist.github.com/nonamelive/9334458

It uses a private API, but I can confirm that it's App Store safe. (One of my apps using this code got approved by the 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;
}
Milan answered 1/4, 2014 at 17:10 Comment(2)
This has been the best solution so far, with some of the others I would either still randomly get the double push issue or I would get a frozen navigation controller.Vertumnus
This code doesn't compile for me, is something missing?Armoured
L
8

I will describe more details about this crash in my app and mark this as answered.

My app has a UINavigationController with the root controller is a UITableViewController that contains a list of note objects. The note object has a content property in html. Select a note will go to the detail controller.

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

Detail controller

This controller has a UIWebView, display the note content passed from the root controller.

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

This controller is the delegate of the webview control. If the note contains links, tap a link will go to the in-app web browser.

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

I received the above crash report everyday. I don't know where in my code caused this crash. After some investigates with the help of a user, I was finally able to fix this crash. This html content will cause the crash:

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

In the viewDidLoad method of the detail controller, I loaded this html to the webview control, right after that, the above delegate method was called immediately with request.URL is the iframe's source (google.com). This delegate method calls pushViewController method while in viewDidLoad => crash!

I fixed this crash by checking the navigationType:

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

Hope this helps

Luca answered 19/2, 2014 at 8:55 Comment(1)
Wouldn't it be a good option to push the controller without animation when called from viewDidLoad?Clearcut
T
6

I had the same issue, what simply worked for me was changing Animated:Yes to Animated:No.

It looks like the issue was due to the animation not completing in time.

Hope this helps someone.

Telluric answered 16/5, 2014 at 16:4 Comment(0)
I
3

To reproduce this bug, try pushing two view controllers at the same time. Or pushing and poping at the same. Example:

enter image description here I have created a category which intercepts these calls and makes them safe by making sure that no other pushes are happening while one is in progress. Just copy the code into your project and due to method swizzling you'll be good to go.

#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
Indigenous answered 5/5, 2014 at 14:42 Comment(3)
One problem with this solution is that if you call popToRootViewController or popToViewController: when you're already on the root view controller or on the viewController be popped to, then didShowViewController won't be called and it will be stuck in viewTransitionInProgress.Cowry
Can you explain these lines: self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController; [self.interactivePopGestureRecognizer setEnabled:YES]; When was the recognizer disabled? And how do you know what the delegate should be? With those lines in, for me it breaks the pop gesture after popping once.Cowry
I tried implementing this and after awhile it locks up the navigation controller, probably due to what @Cowry mentioned.Vertumnus
C
3

Just experienced this issue as well. Let me show you my 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)
 }

The error comes up due to this line :

firstView.addSubView(firstView)

You can't add self to subview. I changed the line of code to :

firstView.addSubView(secondView)

The error went away and I was able to see both of the views. Just thought this would help anyone who wanted to see an example.

Caprification answered 24/2, 2018 at 21:55 Comment(1)
I also tried this approach but the stacktrace would be different and actually show the line of your code causing the crash. I believe the source issue is different from the question.Portly
E
2

Sometimes you mistakenly tried to add a view to its own view.

halfView.addSubview(halfView)

change this to your sub view.

halfView.addSubview(favView)
Emory answered 23/1, 2019 at 6:42 Comment(0)
N
2

I also encountered this problem. When I did Firebase log analysis, I found that this problem only occurs when the app is cold started. So I wrote a demo that can reproduce this crash.

.

I also found that when the window's root viewcontroller is displayed, performing multiple pushes will not cause the same problem again. (You can comment testColdStartUp(rootNav) in AppDelegate.swift, and uncomment the testColdStartUp() comment in ViewController.swift)

ps: I analyzed the scene of this crash in my app. When the user clicks the push notification to cold start the app, the app is still on the Launch page and clicks another push to jump. At this time, the app may appear the Crash. My current The solution is to cache the push or Universal link cold start to open the App jump page, wait for the rootviewcontroller to display, and then delay execution.

Nagging answered 5/6, 2020 at 13:41 Comment(0)
P
1

Search your code for "addSubview".

In one of the places you called this method you tried to add a view to its own sub views array using this method.

For example:

[self.view addSubview:self.view];

Or:

[self.myLabel addSubview:self.myLabel];
Pyszka answered 24/10, 2013 at 7:59 Comment(1)
Happy to hear you found your error, and now I understand exactly why you received the "Can't add self as subview". At a point where your View2 was the root view controller of your navigation controller you pushed View2 which caused this : [View2.view addSubview:View2.view] thus, adding self as subview.Pyszka
C
1

I think that pushing/popping view controllers with animation at any point should be perfectly fine and the SDK should graciously handle the queue of calls for us.

Hence it doesn't and all the solutions try to ignore subsequent pushes, which could be considered a bug since the final navigation stack is not what the code intended.

I implemented a push calls queue instead:

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

It doesn't handle mixed queues of push and pops but it's a good starter to fix most of our crashes.

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

Clearcut answered 15/8, 2014 at 3:18 Comment(2)
I've been trying your solution which seems very good, but I'm having a problem. When pushing 2 view controllers with animated NO one after the other, I very briefly see the first one. This didn't happen before. Any idea what I can do to fix it?Theory
I'm trying to build a project that can consistently cause this sort of crash (my real project gets crash reports like this). I did a simple app with a navigation controller, root controller, and a button that immediately pushes 4 new view controllers onto the nav stack, and then pops off the last one. Without any special subclassing or anything it actually seems to work fine. Did Apple fix this recently?Malik
P
1

Sorry for being late for the party. I recently had this issue wherein my navigationbar goes into corrupted state because of pushing more than one view controller at the same time. This happens because the other view controller is pushed while the first view controller is still animating. Taking hint from the nonamelive answer I came up with my simple solution that works in my case. You just need to subclass UINavigationController and override the pushViewController method and check if previous view controller animation is finished as yet. You can listen to the animation completion by making your class a delegate of UINavigationControllerDelegate and setting the delegate to self.

I have uploaded a gist here to make things simple.

Just make sure you set this new class as the NavigationController in your storyboard.

Pacificia answered 28/2, 2015 at 5:37 Comment(1)
So far it seems to have fixed the crashes on the app I was working on... plus, the solution is pretty straightforward and clear about the point: first viewcontroller animation was not complete yet. People having the same problem should check this out.Metacarpus
M
0

Based on @RobP great hint I made UINavigationController subclass in order to prevent such problems. It handles pushing and/or popping and you can safely execute:

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

If 'acceptConflictingCommands' flag it true(by default) user will see animated pushing of vc1, vc2, vc3 and then will see animated popping of vc3. If 'acceptConflictingCommands' is false, all push/pop requests will be discarded until vc1 is fully pushed - hence other 3 calls will be discarded.

Manners answered 22/4, 2015 at 14:22 Comment(3)
are those commands actually conflicting? I just threw together a quick new project to see this crash happen, using code like you have above (but in Swift), and it actually executed each push, and the pop, all in sequence. one after the other. No crash. Without using any subclasses. just apple's regular UINavigationController.Malik
It was indeed crashing with ObjC and iOS 7. I cannot confirm whether it still occurs now. Are you sure you are executing the commands with animated:true flag?Manners
Yes I was using the animated:true flag.Malik
E
0

nonamelive's solution is awesome. But if you don't want to use the private api, you can just achieve the UINavigationControllerDelegate method.Or you can change the animated YES to NO. Here is a sample of code, you can inherit it. Hope it's helpful : )

https://github.com/antrix1989/ANNavigationController

Esbensen answered 9/11, 2015 at 3:3 Comment(0)
A
0

I had searched this problem a lot, it maybe pushing two or more VC at same time, which cause the pushing animation problem, you can refer to this :Can't Add Self as Subview 崩溃解决办法

just make sure the there is one VC on transition progress at same time,good luck.

Ashliashlie answered 4/7, 2017 at 6:35 Comment(0)
D
-2

try your navigation using delay method, for completing the last navigation animation,

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

Dished answered 31/12, 2013 at 8:56 Comment(0)
C
-2

A view can not be added as a subview in it self.

The views maintains a parent-child hierarchy so if you add a view as a subview in itself it will through exception.

if a class is UIViewController then to get its view you use self.view.

if a class is UIView Class then to get its view you use self.

Celestaceleste answered 31/1, 2014 at 5:14 Comment(0)
S
-3

you cannot add self as subview if it is going to be a UiViewController Class. you can add self as subview if it is going to be a UiView Class.

Stupefaction answered 30/12, 2013 at 10:25 Comment(0)
B
-9

If you like to add a Subview to a View you can do it like this;

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
Beaton answered 24/10, 2013 at 8:8 Comment(4)
Well done, it's a nice piece of code. Now can you tell me what this has to do with this question and how it solves it?Disarmament
@Disarmament Do you have a better idea?Laing
No, because they haven't provided enough information/code to replicate the problem. So there is no way this could be answered, it just looks like your telling them how to do something that has nothing to do with their issue.Disarmament
I guess it was much more helpful to them to just close the issue. Got it! +1 for David G for actually trying to help someone on StackOverflow. I wish I could -1 your Close Votes!!! This is still happening for users and it may be a bug in iOS7 for all we know. So just because someone can't post the offending code doesn't mean the question is not valid and valuable for other users to see. Even if it is just to see that other people are seeing the same issue without any logical reason why they are seeing it. -rrhNoumenon

© 2022 - 2024 — McMap. All rights reserved.