Ошибка приложения iOS. Невозможно добавить себя как 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. Кто-нибудь испытывает этот странный крах?

UPDATE:

Я не знаю, где в моем коде произошел этот сбой, поэтому я не могу опубликовать код здесь, извините.

Второе ОБНОВЛЕНИЕ

См. ответ ниже.

Ответ 1

Я расскажу подробнее об этом сбое в своем приложении и пометьте это как ответ.

У моего приложения есть UINavigationController с корневым контроллером - это UITableViewController, который содержит список объектов заметок. Объект note имеет свойство content в html. Выберите заметку, чтобы перейти к контроллеру детали.

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

Этот контроллер является делегатом элемента управления веб-просмотром. Если заметка содержит ссылки, нажмите ссылку, которая появится в веб-браузере в приложении.

- (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, сразу после этого вышеупомянутый метод делегата был вызван немедленно с запросом .URL является источником iframe (google.com). Этот метод делегата вызывает метод pushViewController, а в viewDidLoad = > crash!

Я исправил эту ошибку, проверив navigationType:

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

Надеюсь, что это поможет

Ответ 2

Я размышляю о чем-то подобном, который я недавно отлаживал... если вы нажмете (или поп) контроллер представления с помощью Animated: YES, он не будет завершен сразу, а плохие вещи произойдут, если вы сделаете еще один push или pop до завершения анимации. Вы можете легко проверить, действительно ли это происходит, временно изменив ваши операции Push и Pop на Animated: NO (чтобы они выполнялись синхронно) и видя, устраняет ли это крах. Если это действительно ваша проблема, и вы хотите включить анимацию, тогда правильная стратегия заключается в реализации протокола UINavigationControllerDelegate. Это включает в себя следующий метод, который вызывается после завершения анимации:

navigationController:didShowViewController:animated:

В основном вы хотите переместить некоторый код по мере необходимости в этот метод, чтобы гарантировать, что никакие другие действия, которые могут вызвать изменение в стеке NavigationController, не будут выполняться до тех пор, пока анимация не будет завершена, и стек не будет готов для дополнительных изменений.

Ответ 3

Мы тоже начали эту проблему, и вероятность того, что наша проблема была вызвана одной и той же проблемой, была весьма вероятной.

В нашем случае нам пришлось в некоторых случаях извлекать данные с задней части, что означало, что пользователь мог бы что-то нажать, а затем произошла небольшая задержка до наступления навигационного перехода. Если пользователь быстро нажимал на них, они могли бы получить два навигационных нажатия от одного и того же контроллера представлений, что вызвало это самое исключение.

Наше решение - это категория в 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 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:

@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: версия для этих случаев.)

Ответ 4

Этот код решает проблему: 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;
}

Ответ 5

У меня была одна и та же проблема, что просто сработало для меня, изменилось Анимированная: Да, чтобы анимировать: Нет.

Похоже, проблема была связана с тем, что анимация не завершилась вовремя.

Надеюсь, это поможет кому-то.

Ответ 6

Чтобы воспроизвести эту ошибку, попробуйте одновременно нажать два контроллера представления. Или толкать и нажимать на то же самое. Пример:

enter image description here  Я создал категорию, которая перехватывает эти вызовы и делает их безопасными, следя за тем, чтобы во время выполнения других действий не было других нажатий. Просто скопируйте код в свой проект, и из-за метода swizzling вам будет хорошо идти.

#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

Ответ 7

Найдите свой код для "addSubview".

В одном из мест, которые вы назвали этим методом, вы попытались добавить представление в свой собственный массив подменю, используя этот метод.

Например:

[self.view addSubview:self.view];

Или:

[self.myLabel addSubview:self.myLabel];

Ответ 8

Я думаю, что толкающие /popping контроллеры представлений с анимацией в любой точке должны быть совершенно точными, и SDK должен любезно обрабатывать очередь вызовов для нас.

Следовательно, это не так, и все решения пытаются игнорировать последующие нажатия, которые можно считать ошибкой, поскольку последний стек навигации не является тем, что предназначался для кода.

Вместо этого я реализовал очередь push-вызовов:

// 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, но это хороший стартер, чтобы исправить большинство наших сбоев.

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

Ответ 9

Извините за опоздание на вечеринку. У меня недавно была эта проблема, когда моя навигационная панель переходит в поврежденное состояние из-за одновременного нажатия нескольких контроллеров. Это происходит потому, что другой контроллер представления нажат, пока первый контроллер просмотра все еще оживляет. Принимая намек на неадекватный ответ, я придумал свое простое решение, которое работает в моем случае. Вам просто нужно подклассом UINavigationController и переопределить метод pushViewController и проверить, завершена ли предыдущая анимация контроллера. Вы можете прослушать завершение анимации, сделав свой класс делегатом UINavigationControllerDelegate и установив делегата на self.

Я загрузил gist здесь, чтобы сделать вещи простыми.

Просто убедитесь, что вы установили этот новый класс в качестве NavigationController в своем раскадровке.

Ответ 10

Только что столкнулся с этой проблемой. Позвольте мне показать вам мой код:

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)

Ошибка ушла, и я смог увидеть оба мнения. Просто подумал, что это поможет любому, кто хочет увидеть пример.

Ответ 11

Основываясь на большой подсказке @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 равен true (по умолчанию), пользователь увидит анимированное нажатие vc1, vc2, vc3, а затем увидит анимированное высказывание vc3. Если "acceptConflictingCommands" является ложным, все push/pop-запросы будут отброшены до тех пор, пока vc1 не будет полностью нажат - следовательно, остальные 3 вызова будут отброшены.

Ответ 12

неамелевое решение является удивительным. Но если вы не хотите использовать private api, вы можете просто достичь метода UINavigationControllerDelegate. Или вы можете изменить анимированный YES на NO. Вот пример кода, вы можете наследовать его. Надеюсь, что это полезно:)

https://github.com/antrix1989/ANNavigationController

Ответ 13

Я много раз искал эту проблему, возможно, нажав одновременно два или более VC, которые вызывают проблему pushing animation, вы можете ссылаться на это: Невозможно добавить себя как Subview 崩溃 解决 办法

просто убедитесь, что в то же время есть один VC при переходе, удачи.

Ответ 14

Иногда вы по ошибке пытались добавить представление к собственному представлению.

halfView.addSubview(halfView)

измените это на ваш подвид.

halfView.addSubview(favView)

Ответ 15

попробуйте свою навигацию с использованием метода задержки, для завершения последней анимации навигации,

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

Ответ 16

Невозможно добавить в него представление в виде subview.

Представления поддерживают иерархию parent-child, поэтому, если вы добавите представление как subview в себя, оно будет через исключение.

Если класс UIViewController, то для его просмотра вы используете self.view.

если класс - это класс UIView, то для его просмотра вы используете self.

Ответ 17

вы не можете добавить self как subview, если он будет классом UiViewController. вы можете добавить self как subview, если он будет классом UiView.

Ответ 18

Если вы хотите добавить 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