Приложение iOS зависает на PushViewController

Мой навигационный контроллер периодически зависает при нажатии. Кажется, что новый контроллер представления добавляется в стек, но анимация так и не происходит. У меня также есть два других контейнера, которые содержат контроллеры представления на экране, и я могу нормально взаимодействовать с ними обоими после зависания контроллера навигации. Что действительно интересно, если я попытаюсь поместить другой контроллер представления в стек навигационного контроллера, я заметил, что поверх стека находится дополнительный контроллер представления (контроллер представления, который я изначально задвинул, который заморозил навигационный контроллер). Итак, если я нахожусь на домашнем экране (мы назовем его VC-Home) и пытаюсь нажать новый вид (VC-1), а он зависает, то я пытаюсь нажать новый вид (VC-2), это то, что я вижу в текущем стеке перед отправкой:

{ [VC-Home, VC-1] }

и после вызова pushViewController он остается прежним; VC-2 не добавляется в стек.

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

Я создаю новый контроллер представления из раскадровки, вызывая UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController"), поэтому я не думаю, что там есть какие-то проблемы. Я также не переопределяю pushViewController на панели навигации. Некоторые уникальные особенности моего приложения заключаются в том, что в нем очень много изображений с высоким разрешением (используя SDWebImage для управления этим), и у меня всегда есть три контейнера на экране одновременно (один контроллер навигации, один контроллер представления для поиска и один интерактивный желоб/ боковое/выдвижное меню).

Использование ЦП низкое, а использование памяти нормальное (стабильно около 60-70 МБ на устройстве, когда происходят зависания).

Есть ли какие-либо идеи о том, что может быть причиной этого, или какие-либо советы по отладке, которые могут помочь мне обнаружить реальную проблему?

Обновить

Для UINavigationController нет уникального кода, так как я просто использую pushViewController(). Вот код, который его вызывает:

func didSelectItem(profile: SimpleProfile) {
     let vc = UIStoryboard.profileViewController()
     vc.profile = profile
     navigationController?.pushViewController(vc, animated: true)
}

ViewController, который я нажал, имеет следующий код в viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()
    button.roundView()

    if let type = profile?.profileType {
        //load multiple view controllers into a view pager based on type 
        let viewControllers = ProfileTypeTabAdapter.produceViewControllersBasedOnType(type)
        loadViewPagerViews(viewControllers)

        let topInset = headerView.bounds.height + tabScrollView.contentSize.height
        if let viewPager = viewPager {
            for view in viewPager.views {
                if let tempView = view as? PagingChildViewController {
                    tempView.profile = fullProfile
                    tempView.parentVCDelegate = self
                    tempView.topInset = topInset
                }
            }
        }
    }
}

func loadViewPagerViews(viewControllers: [UIViewController]) {
    viewPager?.views = viewControllers
    viewPager?.delegate = self

    //loading views into paging scroll view (using PureLayout to create constraints)
    let _ = subviews.map { $0.removeFromSuperview() }
    var i = 0
    for item in views {
        addSubview(item.view)
        item.view.autoSetDimensionsToSize(CGSize(width: tabWidth, height: tabHeight))
        if i == 0 {
            item.view.autoPinEdgeToSuperviewEdge(.Leading)
        } else if let previousView = views[i-1].view {
            item.view.autoPinEdge(.Leading, toEdge: .Trailing, ofView: previousView)
        }
        if i == views.count {
            item.view.autoPinEdgeToSuperviewEdge(.Trailing)
        }
        i += 1
    }

    contentSize = CGSize(width: Double(i)*Double(tabWidth), height: Double(tabHeight))
}

Обновление 2

Наконец-то я снова заморозил его. Приложение было в фоновом режиме, и я вернул его и попытался поместить контроллер представления в стек, когда он завис. Я заметил, что происходит анимация. У меня есть прокрутка в верхней части страницы, которая просматривает ее содержимое каждые 10 секунд (подумайте о верхнем баннере приложения). Во время этой заморозки я заметил, что баннер находится в середине анимации.

Вот функция прокрутки из моего UIScrollView, которая вызывается каждые 10 секунд:

func moveToNextItem() {
    let pageWidth: CGFloat = CGRectGetWidth(frame)
    let maxWidth: CGFloat = pageWidth * CGFloat(max(images.count, profileImages.count))
    let contentOffset: CGFloat = self.contentOffset.x
    let slideToX = contentOffset + pageWidth

    //if this is the end of the line, stop the timer
    if contentOffset + pageWidth == maxWidth {
        timer?.invalidate()
        timer = nil
        return
    }

    scrollRectToVisible(CGRectMake(slideToX, 0, pageWidth, CGRectGetHeight(frame)), animated: true)
}

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

Я также перепроверил стек, и та же ситуация, что описана выше, по-прежнему имеет место, когда [VC-Home, VC-1] является стеком, а VC-2 не проталкивается. Я также просмотрел переменные VC-1, и все загрузилось (вызовы данных и загрузка изображений).

Обновление 3

Это становится странным с каждой секундой. Я переопределил pushViewController, чтобы поставить там точку останова и выполнить некоторую отладку на основе ответа Алессандро Орнано. Если я безуспешно нажимаю контроллер представления, затем отправляю свое приложение в фоновый режим, помещаю точку останова в вызов pushViewController и возвращаю приложение обратно, точка останова сразу же срабатывает несколько раз. Если я затем продолжу мимо всех попаданий, следующий контроллер представления внезапно станет видимым, а последний контроллер представления, который я пытался нажать, теперь находится в стеке как последний контроллер представления. Это означает, что тот, который я вижу, по-прежнему отключен, что, по сути, ставит меня в то же положение, что и раньше.


person Maxwell    schedule 08.04.2016    source источник
comment
Можете ли вы подробно описать методы VC-1, такие как viewWillAppear, viewDidAppear или viewDidDisappear? Есть ли в основном потоке инструкции, нарушающие структуру макета?   -  person Alessandro Ornano    schedule 12.04.2016
comment
Я согласен @AlessandroOrnano, вы в той же теме?   -  person bourvill    schedule 12.04.2016
comment
Я только переопределяю viewDidAppear, но я просто устанавливаю логическое значение и выношу подвид на передний план. Там нет фоновых тем. Я буду продолжать искать что-нибудь в фоновом потоке, что может быть подозрительным, и обновлю свой вопрос, если что-нибудь найду.   -  person Maxwell    schedule 12.04.2016
comment
стабильно в районе 80-100 мб... не думаю что это нормально, просто если это игра   -  person Andrea    schedule 12.04.2016
comment
Не могли бы вы показать какой-нибудь связанный код?   -  person Sulthan    schedule 12.04.2016
comment
Добавлен код @Sulthan. Я разместил то, что считаю важным, но если есть какие-либо особенности, которые вы хотели бы увидеть, дайте мне знать, и я опубликую это.   -  person Maxwell    schedule 12.04.2016


Ответы (5)


Мы столкнулись с той же проблемой пару недель назад. И для нашей проблемы мы сузили его до left-edge pop gesture recogniser. Вы можете попробовать и проверить, можете ли вы воспроизвести эту проблему, выполнив следующие шаги.

  • Попробуйте использовать жест всплывающего окна слева, когда под ним нет контроллеров представления (например, на контроллерах корневого представления ваш VC-Home контроллер)
  • После этого попробуйте щелкнуть любой элемент пользовательского интерфейса.

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

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

- (void)navigationController:(UINavigationController *)navigationController
   didShowViewController:(UIViewController *)viewController
                animated:(BOOL)animate
{
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
{
        if (self.viewControllers.count > 1)
        {
            self.interactivePopGestureRecognizer.enabled = YES;
        }
        else
        {
            self.interactivePopGestureRecognizer.enabled = NO;
        }
    }
}
person Penkey Suresh    schedule 15.04.2016
comment
Ну разве это что-то! Я собираюсь сделать несколько тестов завтра, но это заставляет его зависать очень постоянно. - person Maxwell; 15.04.2016
comment
@ Максвелл, да. Я наблюдал это и в некоторых популярных приложениях в App Store. - person Penkey Suresh; 15.04.2016
comment
Хорошая находка! Спасибо, что поделился. - person Maxwell; 15.04.2016

Отличный ответ от @Penkey Suresh! Спас мой день! Вот версия SWIFT 3 с небольшим дополнением, которое помогло мне:

    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {


    if (navigationController.viewControllers.count > 1)
    {
         self.navigationController?.interactivePopGestureRecognizer?.delegate = self
        navigationController.interactivePopGestureRecognizer?.isEnabled = true;
    }
    else
    {
         self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
        navigationController.interactivePopGestureRecognizer?.isEnabled = false;
    }
}

Только не забудьте добавить UINavigationControllerDelegate и установить navigationController?.delegate = self Еще одна важная часть - назначить interactivePopGestureRecognizer себе или nil соответственно.

person Tim Friedland    schedule 31.07.2017
comment
Мне пришлось преобразовать self в UIGestureRecognizerDelegate. Пример: self.navigationController?.interactivePopGestureRecognizer?.delegate = self as? UIGestureRecognizerDelegate - person lenooh; 23.10.2018
comment
Итак, это удалит фон перехода _UIParallaxView lightGray с vc1 на vc2? потому что это не работает для меня - person Marlhex; 05.08.2019

Я думал о периодическом зависании, основном потоке и SDWebImage.

Предполагая, что вы используете изображение, загруженное из завершенного блока downloadImageWithURL:options:progress:completed:... если это так, убедитесь, что вы отправили его в основную очередь перед использованием изображения.

Если вы используете SDWebImageDownloader напрямую, блок завершения (как вы заметили) будет вызываться в фоновой очереди, вы можете исправить это с помощью dispatch_async в основной очереди из завершения.

В противном случае вы можете использовать: SDWebImageManager downloadImageWithURL:options:progress:completed: (метод, вызывающий блоки завершения в основной очереди).

если проблема не устранена (просто потому, что вы говорите о «…некоторых уникальных вещах в моем приложении, это то, что оно очень тяжелое с изображением ..»), посмотрите также Распространенные проблемы, особенно известные проблемы с обработкой обновления изображения.

Добавьте к своему чеку также этот красивый код фрагмента:

import UIKit.UINavigationController

public typealias VoidBlock = (Void -> Void)

public extension UINavigationController  
{
    public func pushViewController(viewController: UIViewController, animated: Bool, completion: VoidBlock) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }
}

возможно, может помочь понять, завершится ли pushViewController, если закончите со всеми ожидаемыми viewControllers..

Еще один тест, который я пытаюсь сделать, это запустить приложение с iOS 8.x и iPhone 6+, потому что есть некоторые проблемы в проекте pureLayout, связанном с iOS 9. Можете ли вы отправить отзыв об этом тесте?

У меня также есть некоторые подозрения в реальном измерении прокрутки перед действием pushview, можете ли вы проанализировать текущее представление с помощью проверка иерархии представлений?

person Alessandro Ornano    schedule 12.04.2016
comment
Я использую imageView.sd_setImageWithURL(url) для всех своих загрузок изображений, которые запускают для меня блок завершения в основном потоке, поэтому я не ожидаю, что там произойдет какое-либо зависание. - person Maxwell; 12.04.2016
comment
Обновление: я вставил ваш код, и блок завершения выводит на консоль сообщение о завершении перехода. У меня завис навигационный контроллер, но я все еще могу попытаться добавить в стек новые контроллеры представления из одного из других моих контейнеров. Когда я это делаю, блок завершения выполняется, но оператор печати никогда не отображается в консоли. - person Maxwell; 12.04.2016

Пожалуйста, проверьте, нет ли у вас ненужных кодов, как показано ниже, в вашем BaseNavigationViewController:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

Здесь у меня есть пример кода, который воспроизводит эту проблему (зависание приложения при нажатии VC), которую вы должны воспроизвести, выполнив следующие действия:

  • Попробуйте использовать жест всплывающего края (слева | справа), когда под ним нет контроллеров представления (например, на контроллерах корневого представления ваш контроллер VC-Home)
  • Попробуйте щелкнуть любой элемент пользовательского интерфейса после этого (что подтолкнет вас к следующему ViewController).

Пример кода: https://github.com/aliuncoBamilo/TestNavigationPushBug

person Aliunco    schedule 20.06.2018

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

person Okhan Okbay    schedule 04.04.2020