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

Удаление контроллера представления из UIPageViewController

Странно, что нет простого способа сделать это. Рассмотрим следующий сценарий:

  • У вас есть контроллер просмотра страницы с 1 страницей.
  • Добавьте еще одну страницу (всего 2) и выделите ее.
  • Я хочу,, когда пользователь прокручивается назад к первой странице, вторая страница теперь удаляется и освобождается, и пользователь больше не может вернуться на эту страницу.

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

Я хочу сделать это?

4b9b3361

Ответ 1

Хотя ответы здесь все информативны, существует альтернативный способ решения проблемы, приведенный здесь:

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

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

Проблема описана довольно хорошо матовым здесь:

На самом деле это ошибка в UIPageViewController. Это происходит только с помощью стиля прокрутки (UIPageViewControllerTransitionStyleScroll) и только после вызова setViewControllers: направление: анимированное: завершение: с анимированный: ДА. Таким образом, существует два способа обхода:

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

Или, если вы вызываете setViewControllers: direction: animated: completion:, use только анимированные: НЕТ.

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

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

Это хорошее описание, а не проблема, отмеченная в этом вопросе, но очень близкая. Обратите внимание на строку о том, что если вы выполняете setViewControllers с помощью animated:NO, вы заставите UIPageViewController повторно запрашивать свой источник данных в следующий раз, когда пользователь нажимает жест, поскольку он больше не "знает, где он", или какие контроллеры представлений рядом с его текущим контроллером.

Однако это не сработало для меня, потому что были моменты, когда мне нужно программно перемещать PageView вокруг анимации.

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

К сожалению, когда я попытался, я начал получать странные "ошибки утверждения" с контроллера просмотра страницы. Они выглядят примерно так:

*** Ошибка утверждения в [UIPageViewController queueingScrollView:...

Не зная точно, почему это произошло, я отступил и в конце концов начал использовать Jai answer в качестве решения, создав совершенно новый UIPageViewController, нажав его на UINavigationController, затем вытаскивая старый. Гросс, но он работает - в основном. Я обнаружил, что все еще получаю случайные сбои утверждений от UIPageViewController, как этот:

*** Ошибка утверждения в [UIPageViewController queueingScrollView: didEndManualScroll: toRevealView: direction: animated: didFinish: didComplete:], /SourceCache/UIKit _Sim/UIKit-2380.17/UIPageViewController.m:1820 $1 = 154507824 Нет диспетчера просмотра, управляющего видимым видом >

И приложение выйдет из строя. Зачем? Ну, в поисках, я нашел этот другой вопрос, о котором я упомянул выше, и в частности принятый ответ, который защищает моей первоначальной идеей, просто вызывая setViewControllers: animated:YES, а затем, как только он завершает вызов setViewControllers: animated:NO с теми же контроллерами представления, что и reset, UIPageViewController, но у него отсутствовал элемент: вызов этого кода обратно в главную очередь! Здесь код:

__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
                });
            }
        }];

Ничего себе! Единственная причина, по которой это на самом деле имеет смысл для меня, - это то, что я смотрел сессию 211 WWDC 2012, создание параллельных пользовательских интерфейсов на iOS (доступный здесьс учетной записью разработчика). Теперь я помню, что попытка изменить объекты источника данных, на которые зависают объекты UIKit (например, UIPageViewController), и делать это во вторичной очереди, может вызвать некоторые неприятные сбои.

То, что я никогда не видел, особенно задокументировано, но теперь должен предположить, что это так и нужно прочитать, заключается в том, что блок завершения анимации выполняется во вторичной очереди, а не в основной. Поэтому причина, по которой UIPageViewController вызывала крики и давала ошибки утверждения, когда я изначально пытался вызвать setViewControllers animated:NO в блоке завершения setViewControllers animated:YES, а также теперь, когда я просто использую UINavigationController, чтобы нажимать новый UIPageViewController (но делаю это, опять же, в блоке завершения setViewControllers animated:YES) происходит потому, что все это происходит в этой вторичной очереди.

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

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

EDIT: Swift version здесь, если кому-то интересно.

Ответ 2

Завершая ответ Мэтта Мак, следующий метод может быть добавлен в подкласс UIPageViewController, таким образом, позволяя использовать setViewControllers: direction: анимированное: завершение: поскольку оно предназначалось для использования, если ошибка не присутствовала.

- (void) setViewControllers:(NSArray*)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^)(BOOL))completion {

    if (!animated) {
        [super setViewControllers:viewControllers direction:direction animated:NO completion:completion];
        return;
    }

    [super setViewControllers:viewControllers direction:direction animated:YES completion:^(BOOL finished){

        if (finished) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [super setViewControllers:viewControllers direction:direction animated:NO completion:completion];
            });
        } else {
            if (completion != NULL) {
                completion(finished);
            }
        }
    }];
}

Теперь просто вызовите setViewControllers: direction: animated: completion: в классе/подклассах, реализующем этот метод, и он должен работать как ожидалось.

Ответ 3

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

// deleteVC is a child view controller of the UIPageViewController
[deleteVC willMoveToParentViewController:nil];
[deleteVC.view removeFromSuperview];
[deleteVC removeFromParentViewController]; 

Просмотр контроллера deleteVC удаляется из свойства childViewControllers UIPageViewController, но все равно появляется на экране, если пользователь переходит к нему.

Пока кто-то умнее меня найдет изящное решение, вот работа (это взломать - так что вы должны спросить себя, действительно ли вам нужно удалить страницы из UIPageViewController).

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

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

Далее (и здесь взломать), создайте и настройте новый UIPageViewController и загрузите его на переднем плане (т.е. перед другим UIPageViewController). Убедитесь, что новый UIPageViewController начинает показывать ту же страницу, которая была ранее отображена. Ваш новый UIPageViewController будет получать новые контроллеры представлений из источника данных.

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

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

Ответ 4

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

  • Удалить страницу и перейти на следующую страницу
  • В блоке завершения setViewControllers я создал /init 'ed новый UIPageViewController с измененными данными (элемент удален) и нажал его без анимации, поэтому на экране ничего не меняется (мой UIPageViewController содержится в UINavigationController )
  • После нажатия нового UIPageViewController, получите копию массива viewControllers UINavigationController, удалите второй контроллер представления (который является старым UIPageViewController)
  • Нет шага 4 - сделано!

Ответ 5

Для улучшения этого. Вы должны определить, будет ли прокрутка страницыView или нет перед setViewControllers.

var isScrolling = false

func viewDidLoad() {
...

  for v in view.subviews{
    if v.isKindOfClass(UIScrollView) {
      (v as! UIScrollView).delegate = self
    }
  }
}

func scrollViewWillBeginDragging(scrollView: UIScrollView){
    isScrolling = true
}

func scrollViewDidEndDecelerating(scrollView: UIScrollView){
    isScrolling = false
}

func jumpToVC{
    if isScrolling {  //you should not jump out when scrolling
        return
    }
    setViewControllers([vc], direction:direction, animated:true, completion:{[unowned self] (succ) -> Void in
        if succ {
            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                self.setViewControllers([vc], direction:direction, animated:false, completion:nil)
            })
        }
    })
}

Ответ 6

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

- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers {
    self.pageViewControllerTransitionInProgress = YES;
}

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
        self.pageViewControllerTransitionInProgress = NO;
}

И когда я пытаюсь создать контроллер представления, я проверяю, происходит ли переход.

- (void)setCurrentPage:(NSString *)newCurrentPageId animated:(BOOL)animated {

    if (self.pageViewControllerTransitionInProgress) {
        return;
    }

    [self.pageContentViewController setViewControllers:@[pageDetails] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {
    }

}

Ответ 7

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

Ответ 8

На самом деле, я думаю, я решил проблему. Это то, что происходило в моем приложении:

  • Экземпляр подкласса UIViewController был удален из UIPageViewController, удалив его из модели и установив свойство viewControllers UIPageViewController в другой ViewController. Этого достаточно, чтобы выполнить эту работу. Нет необходимости делать какой-либо код ограничения контроллера на этом контроллере.
  • ViewController исчез, но я все еще мог прокручивать его, прокручивая прямо в моем случае.
  • Моя проблема была в этом. Я добавлял пользовательский распознаватель жестов в ViewController, отображаемый UIPageViewController. Этот распознаватель считался сильным свойством контроллера, которому также принадлежал UIPageViewController.
  • ИСПРАВЛЕНИЕ: , прежде чем потерять доступ к ViewController, который был уволен, я убедился, что правильно очистил всю используемую память (dealloc) и удалил распознаватель жестов
  • Ваш пробег может отличаться, но я не вижу смысла, чтобы это решение было неправильным, когда что-то не работает. Я сначала подозреваю свой код:)

Ответ 9

У меня была аналогичная ситуация, когда я хотел, чтобы пользователь мог "нажимать и удалять" любую страницу из UIPageViewController. Немного поработав с этим, я нашел более простое решение, чем описанное выше:

  • Захватите индекс страницы "умирающей страницы".
  • Проверьте, является ли "умирающая страница" последней страницей.
    • Это важно, потому что, если это последняя страница, нам нужно прокрутить влево (если у нас есть страницы "A B C" и удалить C, мы перейдем к B).
    • Если это не последняя страница, мы будем прокручивать вправо (если у нас есть страницы "A B C" и удалить B, мы перейдем к C).
  • Сделайте временный переход в "безопасное" место (в идеале последнее). Используйте анимированный: НЕТ, чтобы это произошло мгновенно.

    UIViewController *jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
    [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
    
  • Удалите выбранную страницу из модели/источника данных.

  • Теперь, если это последняя страница:

    • Отрегулируйте вашу модель, чтобы выбрать страницу слева.
    • Получите диспетчер просмотра слева, обратите внимание, что ЭТО ТАКОЕ ТОЛЬКО ОДИН, что тот, который вы получили на шаге 3.
    • Важно сделать это ПОСЛЕ того, как вы удалили страницу из источника данных, потому что она обновит ее.
    • Перейдите к нему, на этот раз с анимированным: YES.

      jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
      [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
      
  • В случае, если это не последняя страница:

    • Откорректируйте свою модель, чтобы выбрать страницу справа.
    • Вернитесь к viewcontroller справа, обратите внимание, что это не тот, который вы получили на шаге 3. В моем случае вы увидите, что он находится в dyingPageIndex, потому что умирающая страница уже удалена из модели.
    • Опять же, важно сделать это ПОСЛЕ того, как вы удалили страницу из источника данных, потому что она обновит ее.
    • Перейти к нему, на этот раз с анимированным: ДА.

      jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex;
      [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
      
  • Что это! Это хорошо работает в XCode 6.1.1.

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

  • YourUIViewController: с классом ваших отдельных страниц.
  • YourTotalNumberOfPagesFromModel: общее количество страниц в вашей модели
  • [YourModel deletePage:] с кодом для удаления умирающей страницы из вашей модели

    - (void)deleteAViewController:(id)sender {
        YourUIViewController *dyingGroup = (YourUIViewController *)sender;
        NSUInteger dyingPageIndex = dyingGroup.pageIndex;
        // Check to see if we are in the last page as it a special case.
        BOOL isTheLastPage = (dyingPageIndex >= YourTotalNumberOfPagesFromModel.count);
        // Make a temporary jump back - make sure to use animated:NO to have it jump instantly
        UIViewController *jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
        [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
        // Now delete the selected group from the model, setting the target
        [YourModel deletePage:dyingPageIndex];
        if (isTheLastPage) {
            // Now jump to the definitive controller. In this case, it the same one, we're just reloading it to refresh the data source.
            // This time we're using animated:YES
            jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
            [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
        } else {
            // Now jump to the definitive controller. This reloads the data source. This time we're using animated:YES
            jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex];
            [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
        }
    }
    

Ответ 10

NSMutableArray *mArray = [[NSMutableArray alloc] initWithArray:self.userArray];
[mArray removeObject:userToBlock];
self.userArray = mArray;

UIViewController *startingViewController = [self viewControllerAtIndex:atIndex-1];
NSArray *viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];

У меня не было времени прочитать эти комментарии, но это сработало для меня. В основном я удаляю данные (в моем случае пользователь), а затем перехожу на страницу перед этим. Работает как шарм. Надеюсь, это поможет тем, кто ищет быстрое решение.

Ответ 11

В Swift 3.0 он сделал следующее:

fileprivate var isAnimated:Bool = false

override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewControllerNavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {

        if self.isAnimated {
            delay(0.5, closure: { 
                self.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
            })
        }else {
                super.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
        }

    }


extension SliderViewController:UIPageViewControllerDelegate {

    func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
        self.isAnimated = true
    }

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        self.isAnimated = finished
    }
}

И здесь функция задержки

func delay(_ delay:Double, closure:@escaping ()->()) {
    DispatchQueue.main.asyncAfter(
        deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}

Ответ 12

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