Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 615 Vote(s) - 3.47 Average
  • 1
  • 2
  • 3
  • 4
  • 5
iOS app error - Can't add self as subview

#11
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][1]
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


[1]:
Reply

#12
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.
Reply

#13
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:


Reply

#14
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][2] to make things simple.

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


[1]:

[To see links please register here]

[2]:
Reply

#15
Based on @RobP great hint I made [UINavigationController subclass][1] 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.


[1]:

[To see links please register here]

Reply

#16
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 : )

[To see links please register here]


Reply

#17

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 崩溃解决办法][1]


[1]:

[To see links please register here]


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

#18
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.
Reply

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

halfView.addSubview(halfView)

change this to your sub view.

halfView.addSubview(favView)
Reply

#20
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][1] 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.


[1]:

[To see links please register here]

Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through