Помилка програми iOS. Неможливо додати себе як підпідгляд


157

Я отримав цей звіт про збій, але не знаю, як його налагодити.

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. Хтось зазнав цієї дивної аварії?

ОНОВЛЕННЯ:

Я не знаю, де мій код спричинив цю аварію, тому я не можу розмістити код тут, вибачте.

Друге ОНОВЛЕННЯ

Дивіться відповідь нижче.


3
Чи можете ви показати нам свій код?
Девід Гольшаузер

43
Вибачте, але я не розумію вашої реакції. Помилка стека зрозуміла в проблемі. Отже, спочатку ви можете дозволити користувачеві поставити більше коду, як йому було запропоновано (лише за 1 год поставлене запитання, і ви попросите негайно його закрити). По-друге, я отримав безвідмовну заяву без причини, оскільки моя відповідь ясна. Питання "Хто-небудь зазнає цієї дивної аварії?". І я розповів, чому він це отримав. Навіть якщо він спеціально не знаходиться в його коді.
Tancrede Chazallet

9
Це питання є одним правильним. користувач не може вказати точний код помилки в цій ситуації. тому що він не знає, в якому контролері перегляду щось піде не так
Равіндра Багале

16
Ми використовуємо Crashlytics і маємо понад 30 користувачів, які зламали наш додаток із програмою "Не можу додати себе як підрозгляд". З ретроспекції взагалі немає посилання на наш додаток.
Richie Hyatt

49
Голосування за повторне відкриття; люди, що закривають його, не дуже роблять розробку для iOS, мабуть, оскільки це поширена проблема, яку впроваджує iOS7, і вбиває цілу купу додатків, які були чудовими в iOS6 (я бачив це в багатьох проектах різних компаній). Шкода, що це питання є найпопулярнішим в Google, але кілька недалеких людей закрили його.
Адам

Відповіді:


51

Я спекулюю на основі чогось подібного, що я налагодив нещодавно ... якщо ви натискаєте (або вискакуєте) контролер перегляду з анімованими: Так, це не завершується відразу, і погані речі трапляються, якщо ви робите інший натиск чи поп перед анімацією завершує. Ви можете легко перевірити, чи справді це так, тимчасово змінивши ваші операції Push and Pop на Animated: NO (щоб вони завершилися синхронно) і побачив, чи не усуне це збій. Якщо це справді ваша проблема, і ви хочете ввімкнути анімацію, то правильною стратегією є реалізація протоколу UINavigationControllerDelegate. Сюди входить наступний метод, який викликається після завершення анімації:

navigationController:didShowViewController:animated:

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


Назад про iOS 4 - я щось подібне побачив в одному з наших додатків - IIRC, якщо ви вискакували анімовані, а потім негайно натиснули анімований код інтерфейсу, буде сильно вимкнено. Закінчилося лише зміною, щоб ніколи не робити двох анімованих операцій push / pop. Звичайно, з тих пір була переписана вся основна логіка, але не важко повірити, що подібного помилка все ще немає.
Гарячі лизання

У мене було те саме питання. У моєму випадку це сталося тому, що додаток виконав інструкцію, яка змінює інтерфейс нового контролера перегляду [newViewController setLabelTitle:...]відразу після виклику pushViewController за допомогою Animated:YES.І я вирішив перемістити метод setLabelTitle на viewDidLoad на новийViewController. Дякую за те, що ви дали мені поняття.
jeprubio

Рада, що допомогла! Добре сказати, що переміщення коду до нового ViewController - це також варіант, якщо ви знаєте, до якого класу він буде. Все більше і більше мені здається корисним впіймати різні методи протоколу UINavigationControllerDelegate, в будь-якому випадку. І я виявив, що в iOS8 події ведуть різний порядок, і деякі речі, які раніше були менш-менш синхронні, зараз швидко повертаються, але планують робити завдання на тлі асинхронно, створюючи безліч нових помилок синхронізації на кшталт цих. Спасибі, Apple!
RobP

14

Ми також почали отримувати це питання, і, швидше за все, ймовірно, що наші були викликані тією ж проблемою.

У нашому випадку нам доводилося витягувати дані із заднього кінця в деяких випадках, що означало, що користувач може щось натиснути, і тоді буде невелика затримка до того, як відбудеться навігаційний поштовх. Якщо користувач швидко торкається, вони можуть закінчитися двома натисканнями навігації від одного контролера перегляду, що спричинило саме цей виняток.

Наше рішення - це категорія на 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'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: версія справ.)


Оскільки ви сказали, що ви намагаєтеся вирішити це рішення, чи вирішив це питання для вас?
Майк Д

Поки що так. Проблема не виникла. Я оновлю відповідь.
Калле

4
Я використовував модифіковану версію на основі вашої: gist.github.com/mdewolfe/9369751 . Схоже, це виправили.
Майк Д

2
@Kalle Це рішення працює для push / pop. Але як вирішити цю помилку, якщо я використовую segue?
Geek

@Kadle Чи можете ви допомогти мені здійснити це? Дивись stackoverflow.com/q/23247713/1323014 ТНХ
Marckaraujo

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

Це було найкращим рішенням до цих пір, з деякими з інших я б все одно випадково отримав проблему подвійного натискання, або отримав би заморожений навігаційний контролер.
blueice

Цей код не компілюється для мене, щось не вистачає?
Максим В

8

Я опишу більш детальну інформацію про цю аварію у своєму додатку і відзначу це як відповідь.

У моєму додатку є UINavigationController з кореневим контролером - це UITableViewController, який містить список об’єктів примітки. Об'єкт примітки має властивість вмісту в 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 до контролю веб-перегляду, одразу після цього вищевказаний метод делегата був негайно викликаний запитом .URL є джерелом iframe (google.com). Цей метод делегата викликає метод pushViewController, перебуваючи в viewDidLoad => аварія!

Я вирішив цю аварію, перевіривши NaviType:

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

Сподіваюся, це допомагає


1
Чи не було б гарним варіантом натиснути контролер без анімації, коли дзвонить viewDidLoad?
Rivera

6

У мене було те саме питання, те, що просто працювало для мене, - це змінити Анімоване: Так на Анімоване: Ні.

Схоже, проблема виникла через те, що анімація не завершилася вчасно.

Сподіваюся, що це комусь допоможе.


3

Щоб відтворити цю помилку, спробуйте натиснути одночасно два контролери перегляду. Або одночасно штовхати і смикатись. Приклад:

введіть тут опис зображення Я створив категорію, яка перехоплює ці дзвінки та робить їх безпечними, переконуючись, що під час роботи не відбувається інших натискань. Просто скопіюйте код у свій проект, і завдяки шиплячому методу вам буде добре.

#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

Одна з проблем цього рішення полягає в тому, що якщо ви дзвоните popToRootViewControllerабо popToViewController:коли ви вже перебуваєте на кореневому контролері перегляду або на viewController, на який вискакує, він didShowViewControllerне буде викликаний, і він застрягне viewTransitionInProgress.
divergio

1
Чи можете ви пояснити ці рядки: self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)viewController; [self.interactivePopGestureRecognizer setEnabled:YES]; Коли розпізнавач був відключений? І як ви знаєте, яким повинен бути делегат? З цими рядками, для мене він перериває поп-жест після вискакування один раз.
divergio

Я спробував реалізувати це, і через деякий час він блокує контролер навігації, ймовірно, через те, що згадував @divergio.
blueice

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)

Помилка усунулася, і я зміг побачити обидва погляди. Просто подумав, що це допоможе кожному, хто захотів побачити приклад.


Я також спробував такий підхід, але стек-трак був би іншим і фактично показав би рядок вашого коду, що викликає збій. Я вважаю, що питання джерела відрізняється від питання.
Бен

1

Шукайте у своєму коді "addSubview".

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

Наприклад:

[self.view addSubview:self.view];

Або:

[self.myLabel addSubview:self.myLabel];

Радий почути, що ви виявили свою помилку, і тепер я точно розумію, чому ви отримали повідомлення "Не можу додати себе як підзагляд". У момент, коли ваш View2 був контролером кореневого перегляду вашого контролера навігації, ви натиснули View2, що спричинило це: [View2.view addSubview:View2.view]таким чином, додавши self як підпогляд.
Міхал Шац

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


Я спробував ваше рішення, яке здається дуже хорошим, але у мене є проблема. Коли ви натискаєте два контролери перегляду з анімованим НІ один за одним, я дуже коротко бачу перший. Цього раніше не було. Будь-яка ідея, що я можу зробити, щоб виправити це?
січня

Я намагаюся створити проект, який може постійно спричиняти подібний збій (мій реальний проект отримує звіти про аварійне завершення). Я зробив просте додаток з навігаційним контролером, кореневим контролером і кнопкою, яка негайно натискає 4 нові контролери перегляду на стек nav, а потім вискакує останній. Без будь-якого спеціального підкласу або чогось, що насправді, здається, працює добре. Чи Apple це виправила нещодавно?
Крінх

1

Вибачте, що запізнилися на вечірку. Нещодавно у мене виникла ця проблема, коли моя панель навігації переходить у зіпсований стан через натискання більше одного контролера перегляду одночасно. Це трапляється через те, що інший контролер перегляду натискається, коли перший контролер перегляду все ще анімує. Взявши підказку від відповіді nonamelive, я придумав своє просте рішення, яке працює в моєму випадку. Вам просто потрібно підкласифікувати UINavigationControllerта замінити метод pushViewController і перевірити, чи анімація попереднього контролера перегляду закінчена ще. Ви можете прослухати завершення анімації, зробивши свій клас делегатом UINavigationControllerDelegateі встановивши його self.

Я завантажив сюди суть, щоб зробити прості речі.

Просто переконайтеся, що ви встановили цей новий клас як NavigationController у своїй дошці повідомлень.


Поки, здається, виправлені збої в додатку, над яким я працював ... плюс, рішення досить просте і чітке про те: перша анімація viewcontroller ще не була завершена. Люди, які мають таку ж проблему, повинні це перевірити.
тупик

0

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


чи фактично ці команди суперечать один одному? Я просто зібрав швидкий новий проект, щоб побачити цю аварію, використовуючи код, як у вас вище (але в Swift), і він фактично виконував кожен поштовх, і поп, все в послідовності. одна за одною. Ніякої аварії. Без використання підкласів. просто звичайний яблучний UINavigationController.
Cruinh

Це дійсно було збій з ObjC та iOS 7. Я не можу підтвердити, чи все ще відбувається зараз. Ви впевнені, що виконуєте команди з animated:trueпрапором?
hris.to

Так, я використовував анімований: справжній прапор.
Cruinh

0

Рішення "Лілліве" - приголомшливе. Але якщо ви не хочете використовувати приватні api, ви можете просто досягти UINavigationControllerDelegateметоду. Або ви можете змінити анімовані YESна NO. Ось зразок коду, ви можете успадкувати його. Сподіваюся, що це корисно:)

https://github.com/antrix1989/ANNavigationController


0

Я багато шукав цю проблему, можливо, натискання двох або більше VC одночасно, що спричиняє натиснуту проблему анімації, ви можете посилатися на це: Не можу додати себе як Subview Sub 解决 办法

просто переконайтесь, що є одна ВК прогресу переходу одночасно , удачі.


0

Іноді ви помилково намагалися додати перегляд до власного виду.

halfView.addSubview(halfView)

змінити це у своєму поданні.

halfView.addSubview(favView)

0

Я також стикався з цією проблемою. Коли я робив аналіз журналу Firebase, я виявив, що ця проблема виникає лише тоді, коли програма починає холодно. Тому я написав демонстрацію, яка може відтворити цей збій.

.

Я також виявив, що при відображенні кореневого контролера вікна на екрані виконання декількох натискань знову не спричинить ту саму проблему. (Ви можете прокоментувати testColdStartUp (rootNav) в AppDelegate.swift і відмітити коментар testColdStartUp () у ViewController.swift)

ps: Я аналізував місце цієї аварії у своєму додатку. Коли користувач натискає сповіщення про натиск, щоб запустити додаток, програма все ще знаходиться на сторінці "Запуск" і натискає ще один натиск, щоб перейти. Наразі у додатку може з’явитися Збій. Моє поточне рішення - кешувати натискання або Універсальне посилання холодним початком, щоб відкрити сторінку переходу програми, дочекатися відображення кореневого контролера, а потім затримати виконання.


-2

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

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


-2

Перегляд не можна додавати як підвид у ньому самому.

У представленнях підтримується ієрархія батько-дитина, тому якщо ви додасте подання як підвид, воно буде через виняток.

якщо клас UIViewController, то для його перегляду ви використовуєте self.view.

якщо клас - клас UIView, то для його перегляду ви використовуєте self.


-3

ви не можете додати себе як підрозділ, якщо це буде клас UiViewController. ви можете додати себе як підвід, якщо це буде клас UiView.


-9

Якщо ви хочете додати підзор до перегляду, ви можете зробити це так;

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

Молодці, це приємний фрагмент коду. Тепер ви можете сказати мені, що це стосується цього питання і як воно вирішує?
Popeye

@Popeye У вас є краща ідея?
Девід Гольшаузер

Ні, оскільки вони не надали достатньо інформації / коду, щоб повторити проблему. Тож немає можливості відповісти на це, це просто схоже на те, що ти розповідаєш їм, як зробити щось, що не має нічого спільного з їх проблемою.
Popeye

2
Я думаю, їм було набагато корисніше просто закрити питання. Зрозумів! +1 для David G за те, що він фактично намагався допомогти комусь у StackOverflow. Я б хотів, щоб я міг -1 ваші закриті голоси !!! Це все ще відбувається для користувачів, і це може бути помилка в iOS7 для всіх, що ми знаємо. Тож те, що хтось не може опублікувати код, який не порушує, не означає, що питання не є дійсним та цінним для інших користувачів. Навіть якщо просто бачити, що інші люди бачать ту саму проблему без логічної причини, чому вони її бачать. -rrh
Richie Hyatt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.