iOS应用程序错误 – 无法将自己添加为子视图
我收到这个崩溃报告,但我不知道如何debugging它。
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。 任何人都经历这个奇怪的崩溃?
更新:
我不知道在我的代码导致这次崩溃,所以我不能在这里发布的代码,对不起。
第二次更新
请参阅下面的答案。
我推测基于最近我debugging类似的东西…如果你推动(或popup)视图控制器与animation:是它不会马上完成,坏的事情发生,如果你做另一个推动或popupanimation完成。 您可以通过暂时将Push和Pop操作更改为Animated:NO(以便它们同步完成)并查看是否消除崩溃来轻松testing是否确实如此。 如果这确实是你的问题,你想打开animation,那么正确的策略是实现UINavigationControllerDelegate协议。 这包括以下方法,在animation完成后调用:
navigationController:didShowViewController:animated:
基本上,您需要根据需要将一些代码移动到此方法中,以确保在animation完成并且堆栈已准备好进行更多更改之前,不会导致导致对NavigationController堆栈进行更改的其他操作。
我们也开始讨论这个问题,很有可能我们的问题是由同一个问题引起的。
在我们的情况下,在某些情况下,我们不得不从后端提取数据,这意味着用户可能会点击某些内容,然后导航推送发生之前会有一些延迟。 如果用户正在迅速地窃听,他们可能会从同一个视图控制器中导出两个导航button,这触发了这个例外。
我们的解决scheme是UINavigationController上的一个类别,它可以防止压入/popup,除非顶层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]; }];
基本上,规则是:在任何与用户无关的延迟之前,从相关的导航控制器中获取一个锁,并将其包含在呼叫推/拉中。
“locking”这个词可能会稍微差一点,因为它可能会影响到需要解锁的某种forms的locking,但是由于在任何地方都没有“解锁”的方法,因此可能是没问题的。
(作为旁注,“非用户相关的延迟”是代码引起的任何延迟,即任何asynchronous。用户在导航控制器上点击animation推送不计算,并且不需要为那些导航locking版本例)。
这段代码解决了这个问题: 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; }
我将在我的应用程序中描述这个崩溃的更多细节,并将其标记为已回答。
我的应用程序有一个UINavigationController与根控制器是一个UITableViewController包含注释对象的列表。 注释对象在html中具有内容属性。 select一个笔记将进入细节控制器。
- (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控件的代表。 如果笔记包含链接,点击一个链接将进入应用程序的Web浏览器。
- (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 =>崩溃时调用pushViewController方法!
我通过检查navigationType来解决这个崩溃问题:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if (navigationType != UIWebViewNavigationTypeOther) { //go to web browser controller } }
希望这可以帮助
我有同样的问题,只是为我工作是改变animation:是animation:不。
看起来问题是由于animation没有及时完成。
希望这有助于某人。
要重现此错误,请尝试同时按两个视图控制器。 或者推动和popup相同的。 例:
我已经创build了一个拦截这些调用的类别,并通过确保在进行中时没有其他推动正在发生,从而使它们安全。 只需将代码复制到您的项目中,由于方法调整,您将会很好。
#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
我认为,在任何时候推动/popup视图控制器animation应该是非常好的,SDK应该优雅地处理我们的呼叫队列。
因此,它并没有和所有的解决scheme都试图忽略后续的推动,这可能被认为是一个错误,因为最终的导航栈并不是代码所期望的。
我实现了一个推送呼叫队列:
// 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
它不会处理混杂的push和pops队列,但是解决大部分崩溃问题是一个很好的开端。
要点: https : //gist.github.com/rivera-ernesto/0bc628be1e24ff5704ae
对不起,晚会晚了。 我最近有这个问题,其中我的导航栏进入损坏的状态,因为同时推动多个视图控制器。 发生这种情况是因为在第一个视图控制器仍在animation的时候推动了另一个视图控制器。 从nonamelive答案暗示,我想出了我的简单的解决scheme,在我的情况。 您只需要UINavigationController
并覆盖pushViewController方法,并检查之前的视图控制器animation是否已完成。 您可以通过将您的类作为UINavigationControllerDelegate
的委托并将该委托设置为self
来监听animation完成。
我已经在这里上传了一个要点,使事情变得简单。
只要确保在故事板中将这个新类设置为NavigationController。
search您的代码“addSubview”。
在你调用这个方法的地方之一,你尝试使用这个方法添加一个视图到它自己的子视图数组中。
例如:
[self.view addSubview:self.view];
要么:
[self.myLabel addSubview:self.myLabel];
基于@RobP伟大的提示,我做了UINavigationController的子类 ,以防止这样的问题。 它处理推送和/或popup,你可以安全地执行:
[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的animation推送,然后会看到vc3的animationpopup。 如果'acceptConflictingCommands'为false,所有的push / pop请求将被丢弃,直到vc1被完全推送 – 因此其他的3个调用将被丢弃。
nonamelive的解决scheme是真棒。 但是,如果您不想使用私有api,则可以实现UINavigationControllerDelegate
。或者,您可以将animationYES
更改为NO
。 这里是一个代码示例,你可以inheritance它。 希望这有助于:)
我曾经search过这个问题,可能同时推送两个或更多的VC,导致推动animation的问题,你可以参考这个: 不能把自己添加为子视图崩溃解决办法
只要确保在同一时间有一个VC在转换进度,祝你好运。
如果它将成为UiViewController类,则不能将自己添加为子视图。 你可以添加自己作为子视图,如果它将是一个UiView类。
尝试使用延迟方法导航,完成最后一个导航animation,
[self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>]
一个视图不能作为它自己的子视图添加。
视图维护一个父子层次结构,所以如果你添加一个视图作为子视图本身,它将通过例外。
如果一个类是UIViewController,那么得到它的视图,你使用self.view。
如果一个类是UIView类,那么得到它的视图,你使用自我。
如果你想添加一个子视图到一个视图,你可以这样做;
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