161

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.

4

19 回答 19

53

我根据我最近调试的类似内容进行推测...如果您使用 Animated:YES 推送(或弹出)视图控制器,它不会立即完成,如果您在动画之前再次推送或弹出,则会发生坏事完成。您可以通过暂时将 Push 和 Pop 操作更改为 Animated:NO(以便它们同步完成)并查看是否消除了崩溃来轻松测试是否确实如此。如果这确实是您的问题并且您希望重新打开动画,那么正确的策略是实现 UINavigationControllerDelegate 协议。这包括以下方法,在动画完成后调用:

navigationController:didShowViewController:animated:

基本上,您希望根据需要将一些代码移动到此方法中,以确保在动画完成并且堆栈准备好进行更多更改之前,不会发生可能导致 NavigationController 堆栈更改的其他操作。

于 2014-01-20T05:26:33.870 回答
14

我们也开始遇到这个问题,我们的问题很可能是由同样的问题引起的。

在我们的例子中,在某些情况下,我们必须从后端提取数据,这意味着用户可能会点击某些东西,然后在导航推送发生之前会有一点延迟。如果用户快速四处点击,他们可能会从同一个视图控制器中获得两次导航推送,这会触发这个异常。

我们的解决方案是 UINavigationController 上的一个类别,它可以防止推送/弹出,除非顶部 vc 与给定时间点的相同。

.h 文件:

@interface UINavigationController (SafePushing)

- (id)navigationLock; ///< Obtain "lock" for pushing onto the navigation controller

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock; ///< Uses a horizontal slide transition. Has no effect if the view controller is already in the stack. Has no effect if navigationLock is not the current lock.
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock; ///< Pops view controllers until the one specified is on top. Returns the popped controllers. Has no effect if navigationLock is not the current lock.
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated navigationLock:(id)navigationLock; ///< Pops until there's only a single view controller left on the stack. Returns the popped controllers. Has no effect if navigationLock is not the current lock.

@end

.m 文件:

@implementation UINavigationController (SafePushing)

- (id)navigationLock
{
    return self.topViewController;
}

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock
{
    if (!navigationLock || self.topViewController == navigationLock) 
        [self pushViewController:viewController animated:animated];
}

- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated navigationLock:(id)navigationLock
{
    if (!navigationLock || self.topViewController == navigationLock)
        return [self popToRootViewControllerAnimated:animated];
    return @[];
}

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated navigationLock:(id)navigationLock
{
    if (!navigationLock || self.topViewController == navigationLock)
        return [self popToViewController:viewController animated:animated];
    return @[];
}

@end

到目前为止,这似乎已经为我们解决了这个问题。例子:

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

基本上,规则是:在任何与用户无关的延迟之前,从相关的导航控制器中获取锁,并将其包含在对 push/pop 的调用中。

“锁定”这个词的措辞可能有点糟糕,因为它可能暗示发生某种形式的锁定需要解锁,但由于任何地方都没有“解锁”方法,所以可能没问题。

(作为旁注,“非用户相关延迟”是代码导致的任何延迟,即任何异步。用户点击动画推送的导航控制器不算数,并且不需要为那些执行 navigationLock: 版本例。)

于 2014-01-31T16:42:23.897 回答
12

此代码解决了这个问题:https ://gist.github.com/nonamelive/9334458

它使用私有 API,但我可以确认它是 App Store 安全的。(我使用此代码的一个应用程序已获得 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;
}
于 2014-04-01T17:10:43.297 回答
8

我将在我的应用程序中描述有关此崩溃的更多详细信息,并将其标记为已回答。

我的应用程序有一个 UINavigationController 与根控制器是一个 UITableViewController 包含笔记对象的列表。note 对象在 html 中有一个 content 属性。选择一个注释将转到详细控制器。

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

细节控制器

这个控制器有一个 UIWebView,显示从根控制器传来的笔记内容。

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

这个控制器是 webview 控件的委托。如果笔记包含链接,点击链接将转到应用内网络浏览器。

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

我每天都收到上述崩溃报告。我不知道我的代码在哪里导致了这次崩溃。在用户的帮助下进行了一些调查后,我终于能够修复此崩溃。此 html 内容将导致崩溃:

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

在细节控制器的 viewDidLoad 方法中,我将这个 html 加载到 webview 控件中,然后立即使用 request.URL 调用上述委托方法。URL 是 iframe 的源(google.com)。此委托方法在 viewDidLoad => 崩溃时调用 pushViewController 方法!

我通过检查 navigationType 修复了这个崩溃:

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

希望这可以帮助

于 2014-02-19T08:55:46.133 回答
6

我遇到了同样的问题,对我有用的是将 Animated:Yes 更改为 Animated:No。

看起来问题是由于动画没有及时完成。

希望这可以帮助某人。

于 2014-05-16T16:04:59.457 回答
3

要重现此错误,请尝试同时推送两个视图控制器。或者同时推动和弹出。例子:

在此处输入图像描述 我创建了一个类别,它拦截这些调用并通过确保在进行中没有其他推送发生来确保它们安全。只需将代码复制到您的项目中,由于方法混用,您就可以开始了。

#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
于 2014-05-05T14:42:45.533 回答
3

刚刚也遇到了这个问题。让我向您展示我的代码:

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

由于这一行而出现错误:

firstView.addSubView(firstView)

您不能将自己添加到子视图。我将代码行更改为:

firstView.addSubView(secondView)

错误消失了,我能够看到两个视图。只是认为这会对任何想看例子的人有所帮助。

于 2018-02-24T21:55:51.800 回答
2

有时您错误地尝试将视图添加到它自己的视图中。

halfView.addSubview(halfView)

将此更改为您的子视图。

halfView.addSubview(favView)
于 2019-01-23T06:42:49.950 回答
1

在您的代码中搜索“addSubview”。

在您调用此方法的地方之一,您尝试使用此方法将视图添加到其自己的子视图数组中。

例如:

[self.view addSubview:self.view];

或者:

[self.myLabel addSubview:self.myLabel];
于 2013-12-29T22:16:43.077 回答
1

很抱歉聚会迟到了。我最近遇到了这个问题,其中我的导航栏由于同时推送多个视图控制器而进入损坏状态。发生这种情况是因为在第一个视图控制器仍在动画时推送了另一个视图控制器。从 nonamelive 答案中得到提示,我想出了适用于我的简单解决方案。您只需要UINavigationController继承并覆盖 pushViewController 方法并检查之前的视图控制器动画是否已完成。UINavigationControllerDelegate您可以通过将您的类设置为 的委托并将委托设置为来收听动画完成self

我在这里上传了一个要点以使事情变得简单。

只需确保将这个新类设置为故事板中的 NavigationController。

于 2015-02-28T05:37:21.387 回答
1

我认为在任何时候推送/弹出带有动画的视图控制器都应该很好,SDK 应该为我们处理调用队列。

因此它不会,并且所有解决方案都试图忽略后续推送,这可能被认为是一个错误,因为最终的导航堆栈不是代码想要的。

我实现了一个推送调用队列:

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

它不处理混合的推送和弹出队列,但它是修复我们大多数崩溃的良好开端。

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

于 2014-08-15T03:18:33.007 回答
0

基于@RobP 的伟大提示,我创建了UINavigationController 子类以防止此类问题。它处理推送和/或弹出,您可以安全地执行:

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

如果 'acceptConflictingCommands' 标记为真(默认情况下),用户将看到 vc1、vc2、vc3 的动画推送,然后将看到 vc3 的动画弹出。如果 'acceptConflictingCommands' 为假,所有推送/弹出请求将被丢弃,直到 vc1 被完全推送 - 因此其他 3 个调用将被丢弃。

于 2015-04-22T14:22:12.740 回答
0

我也遇到了这个问题。我在做 Firebase 日志分析的时候,发现这个问题只有在应用冷启动的时候才会出现。所以我写了一个可以重现这个崩溃的demo 。

.

我还发现,当窗口的根viewcontroller显示出来的时候,执行多次push就不会再出现同样的问题了。(您可以在 AppDelegate.swift 中注释 testColdStartUp(rootNav),并在 ViewController.swift 中取消注释 testColdStartUp() 注释)

ps:我在我的应用中分析了这次崩溃的场景。当用户点击推送通知冷启动应用时,应用还在Launch页面,点击另一个推送跳转。这时候,app可能会出现Crash。我目前的解决方案是缓存push或者Universal链接冷启动打开App跳转页面,等待rootviewcontroller显示,然后延迟执行。

于 2020-06-05T13:41:32.917 回答
0

这个问题我查了很多,可能是同时推两个或多个VC,导致推动画的问题,可以参考:Can't Add Self as Subview 崩溃解决办法

只要确保同时有一个VC在过渡进度,祝你好运。

于 2017-07-04T06:35:13.943 回答
0

nonamelive 的解决方案很棒。但是如果你不想使用私有api,你可以只实现方法。UINavigationControllerDelegate或者你可以将动画更改YESNO. 这是一个代码示例,您可以继承它。希望它有帮助:)

https://github.com/antrix1989/ANNNavigationController

于 2015-11-09T03:03:30.503 回答
-2

视图不能作为子视图添加到它自身中。

视图维护父子层次结构,因此如果您将视图添加为本身的子视图,它将通过异常。

如果一个类是 UIViewController 那么你使用 self.view 来获取它的视图。

如果一个类是 UIView 类,那么要获得它的视图,你可以使用 self.

于 2014-01-31T05:14:55.973 回答
-2

尝试使用延迟方法进行导航,以完成最后一个导航动画,

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

于 2013-12-31T08:56:39.210 回答
-3

如果要成为 UiViewController 类,则不能将 self 添加为子视图。如果要成为 UiView 类,您可以将 self 添加为子视图。

于 2013-12-30T10:25:09.053 回答
-9

如果您想将子视图添加到视图中,可以这样做;

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
于 2013-10-24T08:08:11.440 回答