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

UIPageViewController перемещается на неправильную страницу со стилем перехода по прокрутке

Мой UIPageViewController отлично работал в iOS 5. Но когда появился iOS 6, я хотел использовать новый стиль перехода с прокруткой (UIPageViewControllerTransitionStyleScroll) вместо стиля завитки страницы. Это заставило мой UIPageViewController сломаться.

Он отлично работает, только после того, как я вызвал setViewControllers:direction:animated:completion:. После этого в следующий раз, когда пользователь прокручивается вручную на одну страницу, мы получаем неправильную страницу. Что здесь не так?

4b9b3361

Ответ 1

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

__weak YourSelfClass *blocksafeSelf = self;     
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished){
            if(finished)
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [blocksafeSelf.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];// bug fix for uipageview controller
                });
            }
        }];

Ответ 2

Это на самом деле ошибка в UIPageViewController. Это происходит только со стилем прокрутки (UIPageViewControllerTransitionStyleScroll) и только после вызова setViewControllers:direction:animated:completion: with animated: YES. Таким образом, есть два обходных пути:

  1. Не используйте UIPageViewControllerTransitionStyleScroll.

  2. Или, если вы вызываете setViewControllers:direction:animated:completion: используйте только animated:NO.

Чтобы ясно увидеть ошибку, вызовите setViewControllers:direction:animated:completion: и затем в интерфейсе (как пользователь) перейдите влево (назад) на предыдущую страницу вручную. Вы вернетесь не к той странице: вообще не к предыдущей, а к той странице, на которой вы были, когда setViewControllers:direction:animated:completion:

Причина ошибки, по-видимому, заключается в том, что при использовании стиля прокрутки UIPageViewController выполняет какое-то внутреннее кэширование. Таким образом, после вызова setViewControllers:direction:animated:completion: он не может очистить свой внутренний кэш. Он думает, что знает, что такое предыдущая страница. Таким образом, когда пользователь переходит влево на предыдущую страницу, UIPageViewController не может вызвать метод pageViewController:viewControllerBeforeViewController: или вызывает его с неверным текущим контроллером представления.

Я разместил фильм, который наглядно демонстрирует, как увидеть ошибку:

http://www.apeth.com/PageViewControllerBug.mov

РЕДАКТИРОВАТЬ Эта ошибка, вероятно, будет исправлена в iOS 8.

РЕДАКТИРОВАТЬ Для другого интересного обходного пути для этой ошибки, см. Этот ответ: fooobar.com/questions/124598/...

Ответ 3

Здесь - это "грубая" суть, которую я собрал. Он содержит альтернативу UIPageViewController, которая страдает от болезни Альцгеймера (т.е. Она не имеет внутреннего кэширования реализации Apple).

Этот класс не является полным, но он работает в моей ситуации (а именно: горизонтальная прокрутка).

Ответ 4

Начиная с iOS 12, проблема, описанная в первоначальном вопросе, кажется, почти решена. Я пришел к этому вопросу, потому что испытал его в своей конкретной ситуации, в которой это все еще происходит, отсюда и слово "почти".

У меня возникла такая проблема: 1) приложение открывалось по глубокой ссылке 2) на основе ссылки приложение должно было переключиться на конкретную вкладку и открыть заданный элемент с помощью нажатия 3) описанная проблема возникла только тогда, когда цель вкладка ранее не была выбрана пользователем (так что UIPageViewController должен был анимировать эту вкладку), и только когда setViewControllers:direction:animated:completion: animated = true 4) после возврата, возвращаемого обратно в контроллер представления, содержащий UIPageViewController, последний оказался большим беспорядком - он представлял совершенно неправильные контроллеры представления, хотя отладка показала, что все было нормально на логическом уровне

Я предположил, что корень проблемы заключался в том, что я очень быстро setViewControllers:direction:animated:completion: контроллер представления после setViewControllers:direction:animated:completion: вызвано, чтобы у UIPageViewController не было возможности что-то завершить (возможно, анимацию, кэширование или что-то еще).

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

 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { ... } 

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

Надеюсь, что это помогает кому-то в подобной ситуации.

Ответ 5

Эта ошибка все еще существует в iOS9. Я использую то же обходное решение, что и Джордж Цифрикас, но версия Swift:

    pageViewController.setViewControllers([page], direction: direction, animated: true) { done in
        if done {
            dispatch_async(dispatch_get_main_queue()) {
                self.pageViewController.setViewControllers([page], direction: direction, animated: false, completion: {done in })
            }
        }
    }

Ответ 6

Потому что pageviewVC вызывает multi childVC, когда проводишь. Но нам просто нужна последняя видимая страница.

В моем случае мне нужно изменить индекс для сегментированного управления при изменении pageView.

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

extension ViewController: UIPageViewControllerDelegate {

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        guard let pageView = pageViewController.viewControllers?.first as? ChildViewController else { return }
        segmentedControl.set(pageView.index)
    }
}

Ответ 7

Еще один простой обходной путь в Swift: просто сбросьте источник данных UIPageViewController. Это, по-видимому, очищает его кеш и работает вокруг ошибки. Здесь метод, чтобы перейти непосредственно к странице, не нарушая последующие пролистывания. Далее m_pages - это массив ваших контроллеров представления. Ниже показано, как найти currPage (индекс текущей страницы).

func goToPage(_ index: Int, animated: Bool)
{
    if m_pages.count > 0 && index >= 0 && index < m_pages.count && index != currPage
    {
        var dir: UIPageViewController.NavigationDirection
        if index < currPage
        {
            dir = UIPageViewController.NavigationDirection.reverse
        }
        else
        {
            dir = UIPageViewController.NavigationDirection.forward
        }

        m_pageViewController.setViewControllers([m_pages[index]], direction: dir, animated: animated, completion: nil)
        delegate?.tabDisplayed(sender: self, index: index)
        m_pageViewController.dataSource = self;
    }
}

Как найти текущую страницу:

var currPage: Int
{
    get
    {
        if let currController = m_pageViewController.viewControllers?[0]
        {
            return m_pages.index(of: currController as! AtomViewController) ?? 0
        }

        return 0
    }
}

Ответ 8

О:

Похоже, Apple заметила, что разработчики используют UIPageViewController в самых разных приложениях, выходящих за рамки изначально предназначенные Apple, основываясь на своих дизайнерских решениях, в первую очередь. Вместо того, чтобы использовать его в линейном режиме жестов ПВХ часто используется для программного перехода к случайным позиции в структурированной среде. Таким образом, они улучшили реализацию UIPageViewController, и теперь класс вызывает оба источника данных DataSource обратные вызовы

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController

после установки нового ContentViewController в UIPageViewController с

[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];

даже если анимированный поворот страниц скорее предполагает линейный прогресс в, например, иерархию страниц, например, книгу или PDF с последовательными страницами. Хотя - я сомневаюсь, что Apple с точки зрения HIG очень любит видеть, что ПВХ используется таким образом, но - он не нарушает совместимость в обратном направлении, это было простое исправление, поэтому - они в конце концов это сделали. На самом деле это всего лишь один вызов одного из двух методов DataSource, который абсолютно не нужен в линейной среде, где страницы (ViewControllers) уже были обналичены для последующего использования.

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

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

Это также может быть полезно для любого другого разработчика-разработчика, который борется с некоторыми сложностями в реализации UIPageViewController!


ОРИГИНАЛЬНЫЙ ОТВЕТ:

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

Конструкция UIPageViewController абсолютно FLAWLESS, и все хаки, которые вы отправляете, чтобы обойти предполагаемую ошибку, - это не что иное, как средства для ваших собственных ошибочных предположений, потому что вы справились с этим в первое место!!!

БЕСПЛАТНО ВСЕ! Вы просто сражаетесь с каркасом. Я объясню, почему!


Существует столько разговоров о номерах страниц и индексах! Это концепции контроллер знает НИЧЕГО! Единственное, что он знает, - это показ некоторый контент (кстати, предоставленный вами как dataViewController), и что он может сделайте что-то вроде анимации справа/слева, чтобы подражать повороту страницы. CURL или SCROLL...!!!

В мире pageViewController существует только текущий SPACE (пусть вызов это просто так, чтобы избежать путаницы со страницами и индексами).

Когда вы первоначально устанавливаете pageViewController, он только об этом думает SPACE. Только когда вы начинаете панорамирование своего вида, он начинает спрашивать его DataSource, что он в конечном итоге должен отображаться в случае, когда должен произойти щелчок влево/вправо. Когда ты начинаешь панорамирование влево, ПВХ сначала запрашивает BEFORE-SPACE, а затем для AFTER-SPACE, в случае, если вы начнете вправо, он делает это наоборот.

После завершенной анимации (новый вид SPACE отображается в виде PVC) ПВХ считает этот SPACE своим новым центром вселенной, и хотя он находится в нем, он спрашивает DataSource о том, о чем он все еще ничего не знает. В случае законченный поворот направо, который он хочет знать о новом пространстве AFTER и в в случае завершенного поворота влево он запрашивает новое пространство BEFORE.

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

Итак, не говорите "какая страница" или "любой индекс" - просто - есть ли BEFORE или a AFTER. Если вы вернете NIL в один из обратных вызовов DataSource, только ПВХ предполагает, что он находится на одной крайности вашего range of SPACES. Если вы вернете NIL на оба обратный вызов предполагает, что он показывает one and only SPACE, и он никогда не будет снова снова вызывайте обратный вызов DataSource! Логика зависит от вас! Вы определяете страниц и индексов в вашем коде! Не ПВХ!!!


Для пользователя класса есть два способа взаимодействия с ПВХ.

  • A pan-gesture that indicates whether a turn to the BEFORE/AFTER space is desired
  • A method - namely setViewControllers:direction:animated:completion:

Этот метод делает то же самое, что и жест панорамы. Вы указываете направлении (например, UIPageViewControllerNavigationDirectionBackward/Forward) для анимации - если есть одно предназначение - что, другими словами, просто означает → собирается BEFORE или AFTER...

Опять же - не упоминание индексов, номеров страниц и т.д.....!!!

Это просто программный способ добиться того же жеста! И ПВХ делает все правильно, показывая старый контент снова, когда вы двигаетесь назад влево, после того, как он переместился вправо в первую очередь. Запомнить - он просто показывает контент (который вы предоставляете) структурированным способом - это 'single page turn' по дизайну!!!

Это концепция поворота страницы - или BOOK, если вам нравится этот термин лучше!

Просто потому, что вы делаете это, отправляя СТРАНИЦУ 8 после СТРАНИЦЫ 1, не означает, что ПВХ заботится вообще о вашем искривленном мнении о том, как книга должна работать. И пользователь вашего приложений нет. Перевертывание вправо и назад влево обязательно должно привести к достижению исходная страница - IF выполнена с анимацией. И вы должны исправить ошибку найти решение для катастрофы. Не обвиняйте его в UIPageViewController. Он делает его работа отлично!

Просто спросите себя - вы бы сделали то же самое с анимацией PAGE-CURL? НЕТ? Ну, и вы не должны с анимацией SCROLL!!! Анимированный поворот страницы - это поворот страницы и только поворот страницы! В любом режиме! И если вы решите оторвать СТРАНИЦУ 2 к СТРАНИЦЕ 7 вашей КНИГИ, это прекрасно! Но просто не ожидайте, что UIPageViewController придумает несуществующую СТРАНИЦА 7, когда вернется к последней странице, если вы не скажете, что все изменилось...


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

И ПВХ даже отлично играет! При переходе на новый SPACE без анимации он попросит вас дальше по дороге для обоих - контроллера BEFORE и AFTER. Таким образом, ваша прикладная логика может идти в ногу с ПВХ...

Но с анимацией, которую вы всегда передаете, перейдите в предыдущее/следующее пространство (BEFORE - AFTER). Так логично, что нет необходимости вообще в ПВХ, чтобы снова спросить о пространстве, которое уже было знает о том, когда анимационная страница поворачивается!!!

Если вы хотите увидеть СТРАНИЦУ 7, когда вы поворачиваете назад влево после анимации с PAGE 1 направо - ну, я бы сказал - это определенно ваша собственная проблема!


И на всякий случай вы ищете лучшее решение, чем "взломать блок" из принятый ответ (потому что с ним вы делаете работу заранее для чего-то, что могло бы возможно, даже не будет использоваться дальше по дороге) используйте делегата распознавателя жестов:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

Установите свой PVC DataViewController здесь (без анимации), если вы действительно собираетесь вернуться слева на СТРАНИЦА 7 и DataSource будет запрашиваться BEFORE и AFTER, и вы можете отправить любую страницу, которая вам нравится! С флагом или иваром, которые вы должны были спрятать, когда делаете свой неконтролируемый переход с СТРАНИЦЫ 1-8, это не должно быть проблемой...


И когда люди продолжают жаловаться на ошибку в ПВХ - делают 2 оборота страницы, когда это Предполагается сделать только один поворот - указать на эту статью.

Такая же проблема - запуск неанимированного setViewControllers: метод в переходном жесте приведет к такому же хаосу. Вы думаете, что вы устанавливаете новый центр - запрашивается DataSource для нового BEFORE - AFTER dataController - вы reset ваш индексный счет... - Ну, это кажется ОК...

Но - после всего этого бизнеса ПВХ заканчивает свой переход/анимацию и хочет знать о следующий (еще неизвестный ему) dataViewController (BEFORE или AFTER), а также запускает DataSource. Это полностью оправдано! Он должен знать, где в его маленьком BEFORE - CENTER - AFTER мир и готов к следующему повороту.

Но ваша программная логика добавляет еще один индекc++ к его логике и внезапно получает 2 оборота страницы!!! И это одно от того, где вы думаете.

И ВЫ должны это учитывать! Не UIPageViewController!!!


Это точно точка DataSourceProtocol, имеющая только два метода! Он хочет быть как можно более общим, оставив вам пространство и свободу, чтобы определить вашу собственную логику и не зацикливаться на чьих-либо других особых идеях и прецедентах! Логика полностью зависит от вас. И только потому, что вы найдете такие функции, как

- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard position:(GSPositionOfDataViewController)position;
- (NSUInteger)indexOfViewController:(DataViewController *)viewController;

во всех экземплярах с копией/вставкой в ​​облаке не обязательно означает, что вы должны есть эту пищу заранее приготовленной пищи! Расширьте их так, как вам нравится! Посмотрите выше - в моей подписи вы найдете аргумент 'position:'! Я продлил это, чтобы узнать позже, если завершенный поворот страницы был правильным или левым поворотом. Потому что делегат, к сожалению, просто говорит вам, завершена ли ваша очередь или нет! Это не говорит вам о направлении! Но это иногда имеет значение для подсчета индексов, в зависимости от вашего приложения...

Иди с ума - они твои...

СЧАСТЛИВЫЙ КОДИРОВАНИЕ!!!