iOS应用程序错误 - 无法将self添加为子视图

时间:2013-10-24 07:59:10

标签: ios iphone objective-c

我收到了此崩溃报告,但我不知道如何调试它。

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

iOS版本为7.0.3。 有人经历过这种奇怪的崩溃吗?

更新:

我不知道我的代码在哪里造成了这次崩溃,所以我不能在这里发布代码,抱歉。

第二次更新

请参阅下面的答案。

19 个答案:

答案 0 :(得分:48)

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

navigationController:didShowViewController:animated:

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

答案 1 :(得分:13)

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

在我们的案例中,我们不得不在某些情况下从后端提取数据,这意味着用户可能会点击某些内容,然后在导航推送发生之前会有一点延迟。如果用户正在快速敲击,他们最终可能会从同一个视图控制器中进行两次导航推送,从而触发了这个异常。

我们的解决方案是UINavigationController上的一个类别,它可以防止推送/弹出,除非顶级vc与给定时间点的顶级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:这些案件的版本。)

答案 2 :(得分: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;
}

答案 3 :(得分:8)

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

我的应用程序有一个UINavigationController,根控制器是一个UITableViewController,它包含一个note对象列表。 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是iframe的源(google.com)。这个委托方法在viewDidLoad =&gt;中调用pushViewController方法。崩溃!

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

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

希望这有帮助

答案 4 :(得分:5)

我有同样的问题,对我来说简单的工作是改变动画:是动画:否。

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

希望这有助于某人。

答案 5 :(得分:3)

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

enter image description here  我创建了一个拦截这些调用的类别,并通过确保在一个正在进行中没有其他推送发生时使它们安全。只需将代码复制到您的项目中,由于方法调整,您就可以开始使用。

#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

答案 6 :(得分:2)

刚刚遇到过这个问题。让我告诉你我的代码:

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)

错误消失了,我能够看到两个视图。只是觉得这可以帮助任何想看一个例子的人。

答案 7 :(得分:1)

在代码中搜索&#34; addSubview&#34;。

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

例如:

[self.view addSubview:self.view];

或者:

[self.myLabel addSubview:self.myLabel];

答案 8 :(得分:1)

很抱歉这次聚会迟到了。我最近有这个问题,我的导航栏因为同时推送多个视图控制器而进入损坏状态。发生这种情况是因为在第一个视图控制器仍处于动画状态时按下了另一个视图控制器。从非常规的答案中得到提示,我想出了一个适用于我的简单解决方案。您只需要子类UINavigationController并覆盖pushViewController方法并检查以前的视图控制器动画是否已完成。您可以通过将您的班级设为UINavigationControllerDelegate的代表并将代理设置为self来听取动画完成情况。

我上传了一个要点here,以简化操作。

请确保将此新类设置为故事板中的NavigationController。

答案 9 :(得分: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

答案 10 :(得分:0)

根据@RobP提示,我提出了UINavigationController subclass以防止此类问题。它处理推送和/或弹出,您可以安全地执行:

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

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

答案 11 :(得分:0)

nonamelive的解决方案非常棒。但是,如果您不想使用私有API,则可以实现UINavigationControllerDelegate方法。或者您可以将动画YES更改为NO。 这是一个代码示例,您可以继承它。 希望它有用:)

https://github.com/antrix1989/ANNavigationController

答案 12 :(得分:0)

我经常搜索这个问题,可能会同时推送两个或更多VC,导致推动动画问题, 你可以参考这个:Can't Add Self as Subview 崩溃解决办法

确保在转换过程中同时存在一个VC,祝你好运。

答案 13 :(得分:0)

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

halfView.addSubview(halfView)

将此更改为您的子视图。

halfView.addSubview(favView)

答案 14 :(得分:0)

我也遇到了这个问题。 当我执行Firebase日志分析时,我发现仅在应用程序冷启动时才会出现此问题。所以我写了a demo可以重现此崩溃。

我还发现,当显示窗口的根ViewController时,多次执行推送不会再次导致相同的问题。 (您可以在AppDelegate.swift中注释testColdStartUp(rootNav),并在ViewController.swift中取消注释testColdStartUp()注释)

ps: 我在我的应用程序中分析了崩溃的场景。当用户单击推送通知以冷启动该应用程序时,该应用程序仍在“启动”页面上,然后单击另一个推送以跳转。此时,该应用可能会出现崩溃。我当前的解决方案是缓存push或Universal link冷启动以打开App跳转页面,等待rootviewcontroller显示,然后延迟执行。

答案 15 :(得分:-2)

视图无法作为子视图添加到其中。

视图维护父子层次结构,因此如果您将视图作为子视图添加到其中,它将通过异常。

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

如果一个类是UIView类,那么为了获得它的视图你使用self。

答案 16 :(得分:-2)

使用延迟方法尝试导航,以完成上一个导航动画

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

答案 17 :(得分:-3)

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

答案 18 :(得分:-9)

如果您想将Subview添加到View中,您可以这样做;

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
相关问题