IOS 应用程序错误-无法将 self 添加为 subview

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

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。 有人经历过这种奇怪的坠机吗?

更新:

我不知道我的代码在哪里导致了这次崩溃,所以我不能在这里发布代码,对不起。

第二次更新

请看下面的答案。

56285 次浏览

如果你想添加一个视图,你可以这样做;

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

在代码中搜索“ addSubview”。

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

例如:

[self.view addSubview:self.view];

或者:

[self.myLabel addSubview:self.myLabel];

如果它是一个 UiViewController 类,则不能将 self 添加为 subview。 你可以添加自我作为子视图,如果它将是一个 UiView 类。

尝试使用延迟方法导航,完成最后的导航动画,

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

我是根据我最近调试过的一个类似的东西来推测的。 如果你用 Animated 按(或弹出)一个视图控制器: 是的,它不会马上完成,如果你在动画完成之前再按一次或弹出,就会发生不好的事情。您可以很容易地测试这是否确实是这种情况,通过临时改变您的推和流行操作为 Animated: NO (这样他们同步完成) ,并看看是否消除了崩溃。 如果这确实是您的问题,并且您希望将动画重新打开,那么正确的策略是实现 UINavigationControllerGenerate 协议。 这包括以下方法,该方法在动画完成后调用:

navigationController:didShowViewController:animated:

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

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

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

如果一个类是 UIViewController,那么要获得它的视图,您可以使用 self. view。

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

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

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

我们的解决方案是 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 调用中。

“ lock”这个词可能有点不恰当,因为它可能暗示有某种形式的锁发生,需要解锁,但由于没有任何“解锁”方法,这可能是好的。

(作为旁注,“与用户无关的延迟”是指代码引起的任何延迟,即任何异步的延迟。用户点击导航控制器,这是动态推不计数,没有必要做的导航锁: 版本为这些情况。)

我将在我的应用程序中描述更多关于这次崩溃的细节,并将其标记为已回答。

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

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

我通过检查导航来修复这个崩溃。类型:

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

希望这个能帮上忙

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

它使用一个私有的 API,但我可以确认它是应用程序商店的安全。(我的一个使用这个代码的应用程序得到了应用程序商店的批准。)

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

要重现这个 bug,请尝试同时推动两个视图控制器。或者一边推一边按。例如:

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

我有同样的问题,什么只是工作,为我改变动画: 是动画: 号码。

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

希望这对谁有帮助。

我认为在任何时候使用动画推送/弹出视图控制器都应该是完全可行的,SDK 应该能够为我们处理调用队列。

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

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

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

抱歉我来晚了。我最近遇到了这个问题,导航栏因为同时推动多个视图控制器而进入损坏状态。这是因为在第一个视图控制器仍然处于动画状态时推动了另一个视图控制器。从这个无用的答案中得到了一些启示,我想出了一个简单的解决方案,对我的情况很有效。您只需要继承 UINavigationController并覆盖 pushViewController 方法,并检查以前的视图控制器动画是否已经完成。您可以通过使您的类成为 UINavigationControllerDelegate的委托并将该委托设置为 self来听取动画完成。

我已经上传了一个要点 给你使事情变得简单。

只要确保在故事板中将这个新类设置为 NavigationController 即可。

根据@RobP 的提示,我做了 UINavigationController 子类,以防止这样的问题。它处理推和/或弹出,你可以安全地执行:

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

如果“接受冲突命令”标记为 true (默认情况下) ,用户将看到 vc1,vc2,vc3的动画推送,然后将看到 vc3的动画弹出。如果“接受冲突命令”为 false,则将丢弃所有推送/弹出请求,直到完全推送 vc1-因此其他3个调用将被丢弃。

Nonamelive 的解决方案太棒了。但是如果您不想使用私有 API,您可以只实现 UINavigationControllerDelegate方法,或者您可以将动画 YES更改为 NO。 下面是一个代码示例,您可以继承它。 希望对你有帮助:)

Https://github.com/antrix1989/annavigationcontroller

这个问题我已经研究了很多,它可能同时推动两个或更多的 VC,这就导致了推动动画的问题, 你可以参考这个: 不能把自己添加为 Subview 崩溃解决办法

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

刚刚也遇到了这个问题,让我给你看看我的代码:

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)

你不能把 self 添加到 subview。我把代码行改为:

firstView.addSubView(secondView)

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

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

halfView.addSubview(halfView)

把这个改成你的子视图。

halfView.addSubview(favView)

我也遇到了这个问题。 当我做 Firebase 日志分析时,我发现这个问题只有在应用程序冷启动时才会发生。所以我编写了 小样可以重现这次崩溃。

.

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

我在应用程序里分析了车祸现场。当用户单击推送通知来冷启动应用程序时,应用程序仍然在 Launch 页面上,并单击另一个推送来跳转。此时,应用程序可能会出现崩溃。我目前的解决方案是缓存推或通用链接冷启动打开应用程序跳转页面,等待 rootviewcontroller 显示,然后延迟执行。