Подтвердить что ты не робот

PageViewController setViewControllers падает с "Недопустимый параметр, не удовлетворяющий: [количество просмотров] == 3"

Я знаю, что есть еще несколько таких вопросов, но я не мог найти решение для своей проблемы. Я использую pageViewController, который отображает разные ViewControllers. Каждый раз, когда движок pageViewController перемещается вперед, я проверяю ввод lastPage. Если это неверно, pageViewController должен вернуться на эту страницу с помощью метода setViewController. Без этого метода все работает нормально, но если я пытаюсь его использовать, приложение вылетает со следующим исключением:

19:48:25.596 Phook[23579:60b] *** Assertion failure in -[_UIQueuingScrollView _replaceViews:updatingContents:adjustContentInsets:animated:], /SourceCache/UIKit_Sim/UIKit-2935.137/_UIQueuingScrollView.m:383  
2014-06-02 19:48:25.600 Phook[23579:60b] *** Terminating app due to uncaught   exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying:   [views count] == 3'

И вот мой код:

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished
   previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
    if (completed && finished)
    {

        RegisterUserPageContent* lastPage = (RegisterUserPageContent*)previousViewControllers[ previousViewControllers.count-1];
        int lastIndex   = lastPage.pageIndex;
        int newIndex    = ((RegisterUserPageContent*)[self.pageViewController.viewControllers objectAtIndex:0]).pageIndex;

        if (newIndex > lastIndex)
        {   // Moved Forward
            if(!lastPage.testInput)
            {
                [self.pageViewController setViewControllers:@[ [self.storyboard instantiateViewControllerWithIdentifier:_pageStoryBoardIDs[0]] ]
                                         direction:UIPageViewControllerNavigationDirectionReverse
                                         animated:YES completion:nil];


            }
        }
        else
        {   // Moved Reverse

        }
    }
}

Как я уже сказал. Я много искал и реализовал некоторые решения, но ничего не помогло. Спасибо.

4b9b3361

Ответ 1

Я столкнулся с той же проблемой и смог ее решить, используя подсказку из этого ответа fooobar.com/questions/210744/.... Просто разместив setViewControllers: direction: анимированный: завершение: код внутри блока dispatch_async в основной очереди исправил его для меня. Для вас это будет выглядеть как

dispatch_async(dispatch_get_main_queue(), ^{
    [self.pageViewController setViewControllers:@[ [self.storyboard instantiateViewControllerWithIdentifier:_pageStoryBoardIDs[0]] ]
                                     direction:UIPageViewControllerNavigationDirectionReverse
                                     animated:YES completion:nil];
});

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

Ответ 2

В этой же проблеме. Решил его, установив первый контроллер содержимого явно на UIPageViewController, когда он впервые загружен (т.е. Внутри: viewDidLoad).

// create first page
UIViewController* firstContentPageController = [self contentControllerAtIndex: 0];
[_paginationController
    setViewControllers: @[firstContentPageController]
    direction: UIPageViewControllerNavigationDirectionForward
    animated: NO
    completion: nil];

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

- (UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
    NSInteger index = (NSInteger)((MyContentController*)viewController).pageIndex;

    return [self contentControllerAtIndex: index - 1];
}


- (UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
    NSInteger index = (NSInteger)((MyContentController*)viewController).pageIndex;

    return [self contentControllerAtIndex: index + 1];
}


- (MyContentController*)contentControllerAtIndex: (NSInteger)index {
    if (index < 0 || _pagesContent.count <= index)
        return nil;

    // create view controller
    MyContentController* contentController = [self.storyboard instantiateViewControllerWithIdentifier: @"MyContentController"];
    contentController.pageIndex = index;
    contentController.content = [_pagesContent objectAtIndex: index];

    return contentController;
}

Причиной этого является то, что протокол UIPageViewControllerDataSource был разработан, чтобы иметь только методы для вытягивания предыдущего/следующего контроллера содержимого, в отличие от вытягивания одного контроллера с определенным индексом. Это нечетное дизайнерское решение, но оговорка заключается в том, что вместо того, чтобы вызываться Cocoa при первом загрузке компонента, вы должны вручную установить его начальное состояние. Плохая структура дизайна здесь ИМХО.

Ответ 3

Ну это 2018 год и ошибка все еще вокруг. Я перепробовал все решения, описанные выше, но у меня не получилось ни одного. И после многих попыток установка параметра анимации в false была для меня тем, что работало в swift 4

let myViewController : UIViewController = orderedViewControllers[vcIndex]
setViewControllers([myViewController], direction: .forward, animated: false, completion: nil);

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

Ответ 4

Версия Swift 4.2 с правильным ответом.

    DispatchQueue.main.async {
        self.pageViewController?.setViewControllers([self.initialViewControllerAtIndex(index: 0)!], direction: .forward, animated: false, completion: nil)
    }

Ответ 5

У меня была такая же ошибка вывода, хотя в моем случае это не совсем то же самое. Я видел эту ошибку, вызывая emailTextField.becomeFirstResponder() в viewDidAppear(:_) в UIViewController внутри a UIPageViewController. Проблема заключалась в том, что я анимационировал родительский UIPageViewController UIView для перемещения некоторых элементов пользовательского интерфейса при появлении клавиатуры.

После того, как я поцарапал себе голову на несколько часов, я обнаружил, что если вы завершите вызов в блоке DispatchQueue.main.async, он больше не сломается.

Моя иерархия и дальнейшее объяснение:

UIViewController: у него есть две кнопки внизу и UIContainerView, которые содержат UIPageViewController. Этот UIViewController прослушивает NSNotification.Name.UIKeyboardWillChangeFrame для перемещения нижних навигационных кнопок при появлении клавиатуры (не пытайтесь изменить размер контейнера, я пробовал это, и он сломался еще больше). Когда одна из кнопок нажата, UIViewController вызывает дочерний элемент UIPageViewController.setViewControllers(_:direction:animated:completion:) с помощью необходимого контроллера представления.

Если вы вызываете этот метод с помощью animated: false, он работает. Если вы активируете вставку UIViewControllers во время анимации UIView для изменения клавиатуры, она прерывается.

Кроме того, каждый UIViewController внутри UIPageViewController также прослушивает NSNotification.Name.UIKeyboardWillChangeFrame, чтобы изменить размер UIScrollView, который обертывает содержимое.

Мой заключительный код внутри каждого UIViewController, встроенный внутри UIPageViewController, следующий:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    DispatchQueue.main.async {
        self.emailTextField.becomeFirstResponder()
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    emailTextField.resignFirstResponder()
}

Ответ 6

Я получил эту проблему сегодня.
Я запускаю анимацию, когда UIPageController переключает viewController.

- (void)setViewControllers:(nullable NSArray<UIViewController *> *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^ __nullable)(BOOL finished))completion;

Программа завершится сбоем, если animation:YES, но если animation:NO все будет хорошо

Я думаю, мы не можем сделать две анимации одновременно. Но если вы хотите animation:YES, вы можете сделать другую анимацию после переключения viewControlle's, например, использовать GCD after.

Ответ 7

Я столкнулся с этой проблемой при установке pageViewController.dataSource = nil чтобы остановить прокрутку, когда пользователь прокручивает на определенную страницу.

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

По крайней мере, для меня решение состоит в том, чтобы убедиться, что pageViewController.dataSource = nil вызывается перед вызовом pageViewController.setViewControllers(...).

Если вы nil его впоследствии, даже в pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool), вы получите исключение упомянутых выше.

Так установить dataSource, что вы хотите, чтобы это было, прежде чем позвонить setViewControllers.

Ответ 8

В дополнение к

DispatchQueue.main.async {
        self.pageViewController?.setViewControllers([self.initialViewControllerAtIndex(index: 0)!], direction: .forward, animated: false, completion: nil)
    }

Я должен был также поместить этот следующий код в DispatchQueue.main.async

DispatchQueue.main.async {
           for v in self.pageViewController!.view.subviews {
                if let sub = v as? UIScrollView {
                    sub.isScrollEnabled = enable
                }
            }
}

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

Ответ 9

У меня была та же проблема, но выполнения в основной очереди было недостаточно.

Есть несколько viewcontrollers, созданных в func pageViewController (_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) → UIViewController?

и

func pageViewController (_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) → UIViewController?

Эти контроллеры могут пытаться манипулировать пользовательским интерфейсом, когда они не видны! Использование viewDidAppear было бы слишком простым решением для этих контроллеров, чтобы заставить их изменять интерфейс только тогда, когда они видны. Поэтому я придумала решение:

  1. Чистый Свифт

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

import UIKit
extension UIViewController {
    var isInWindow: Bool {
        return self.viewIfLoaded?.window != nil
    }
}
  1. RxSwift + RxCocoa
class MyViewControllerThatMightBePaged {

    var hasAppeared = BehaviorRelay<Bool>(value: false)
    var windowObservation: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.

        // If not paged, the View will have a window immediatelly
        hasAppeared.accept(self.parent != nil)
        windowObservation = observe(\.parent, options: [.new], changeHandler: { [weak self] (_, change) in
            self?.hasAppeared.accept(change.newValue != nil)
        })


        hasAppeared.asObservable()
        .distinctUntilChanged()
        .filter({$0})
        .take(1)
            .subscribe(onNext: { [weak self] (_) in
                // DO the initial UI manipulating magic
            }, onError: nil, onCompleted: nil, onDisposed: nil)
        .disposed(by: disposeBag)
    }

    deinit {
        windowObservation?.invalidate()
        windowObservation = nil
    }

}