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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19560198/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:32:54  来源:igfitidea点击:

iOS app error - Can't add self as subview

iosiphoneobjective-c

提问by Arnol

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?

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

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.

请参阅下面的答案。

采纳答案by Arnol

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.

我的应用程序有一个 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];
}

Detail controller

细节控制器

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

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

- (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.

这个控制器是 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;
}

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:

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

...
<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!

在详细信息控制器的 viewDidLoad 方法中,我将这个 html 加载到 webview 控件,紧接着,上面的委托方法立即被调用, request.URL 是 iframe 的来源(google.com)。此委托方法在 viewDidLoad => 崩溃时调用 pushViewController 方法!

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

希望这可以帮助

回答by RobP

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:

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

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.

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

回答by Kalle

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.

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

.h file:

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

.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

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 delaysgrab a lock from the relevant nav controller, and include it in the call to push/pop.

基本上,规则是:在任何与用户无关的延迟之前,从相关的导航控制器中获取一个锁,并将其包含在对 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.)

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

回答by nonamelive

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

此代码解决了该问题: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.)

它使用私有 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;
}

回答by Lion789

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

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

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

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

Hope this helps someone.

希望这可以帮助某人。

回答by dan

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

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

enter image description hereI 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

回答by halapgos1

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 :

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

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.

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

回答by Rivera

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.

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

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.

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

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

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

回答by nikhil.thakkar

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 UINavigationControllerand 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 UINavigationControllerDelegateand setting the delegate to self.

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

I have uploaded a gist hereto make things simple.

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

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

只需确保将此新类设置为故事板中的 NavigationController 即可。

回答by Michal Shatz

Search your code for "addSubview".

在您的代码中搜索“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];