UIPageViewController, как я могу правильно перейти на определенную страницу, не нарушая порядок, указанный источником данных?
Я нашел несколько вопросов о том, как сделать переход UIPageViewController
на определенную страницу, но я заметил дополнительную проблему с прыжком, что ни один из ответов, похоже, не подтверждает.
Не вдаваясь в подробности моего приложения iOS (которое похоже на кадрированный постраничный календарь), вот что я испытываю. Я объявляю UIPageViewController
, устанавливаю текущий контроллер представления и реализую источник данных.
// end of the init method
pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
pageViewController.dataSource = self;
[self jumpToDay:0];
}
//...
- (void)jumpToDay:(NSInteger)day {
UIViewController *controller = [self dequeuePreviousDayViewControllerWithDaysBack:day];
[pageViewController setViewControllers:@[controller]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSInteger days = ((THDayViewController *)viewController).daysAgo;
return [self dequeuePreviousDayViewControllerWithDaysBack:days + 1];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
NSInteger days = ((THDayViewController *)viewController).daysAgo;
return [self dequeuePreviousDayViewControllerWithDaysBack:days - 1];
}
- (UIViewController *)dequeuePreviousDayViewControllerWithDaysBack:(NSInteger)days {
return [[THPreviousDayViewController alloc] initWithDaysAgo:days];
}
Редактировать Примечание: я добавил упрощенный код для метода dequeuing. Даже при этой богохульной реализации у меня есть та же самая проблема с порядком страниц.
Инициализация работает как и ожидалось. Инкрементный пейджинг работает отлично. Проблема в том, что если я когда-либо позвоню jumpToDay
, порядок будет запутан.
Если пользователь находится в день -5 и переходит к 1-му дню, прокрутка влево показывает день -5 снова вместо соответствующего дня 0. Это, похоже, имеет отношение к тому, как UIPageViewController
хранит ссылки на рядом с ними, но я не могу найти ссылку на метод, который заставил бы его обновить кеш.
Любые идеи?
Ответы
Ответ 1
Программирование iOS6, Мэтт Нойбург документирует эту точную проблему, и я действительно обнаружил, что его решение кажется немного лучше, чем принятый в настоящее время ответ. Это решение, которое отлично работает, имеет негативный побочный эффект анимации изображения до/после, а затем резкой замены этой страницы на нужную страницу. Я чувствовал, что это был странный пользовательский опыт, и решение Matt позаботится об этом.
__weak UIPageViewController* pvcw = pvc;
[pvc setViewControllers:@[page]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES completion:^(BOOL finished) {
UIPageViewController* pvcs = pvcw;
if (!pvcs) return;
dispatch_async(dispatch_get_main_queue(), ^{
[pvcs setViewControllers:@[page]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO completion:nil];
});
}];
Ответ 2
Итак, я столкнулся с той же проблемой, что и вам, когда мне нужно было "перейти" на страницу, а затем обнаружил, что "порядок испортился", когда я указал на страницу. Насколько я мог сказать, контроллер просмотра страниц определенно кэширует контроллеры представлений, и когда вы переходите на страницу, вы должны указать направление: вперед или назад. Затем он предполагает, что новый контроллер представления является "соседним" с предыдущим контроллером представления и, следовательно, автоматически представляет предыдущий контроллер представления, когда вы показываете назад. Я обнаружил, что это происходит только тогда, когда вы используете UIPageViewControllerTransitionStyleScroll
, а не UIPageViewControllerTransitionStylePageCurl
. Стиль скручивания страниц, по-видимому, не делает того же кэширования, поскольку, если вы "прыгаете" на страницу, а затем жесты назад, он передает сообщение pageViewController:viewController(Before/After)ViewController:
источнику данных, позволяя вам предоставить правильный контроллер соседа.
Решение:
При выполнении "перехода" на страницу вы можете сначала перейти на соседнюю страницу на страницу (animated:NO
), с которой вы прыгаете, а затем в блок завершения этого перехода, перейти на нужную страницу. Это приведет к обновлению кеша, так что когда вы вернетесь назад, отобразится правильная страница соседа. Недостатком является то, что вам нужно будет создать два контроллера вида; тот, с которым вы прыгаете, и тот, который должен отображаться после жестов.
Вот код для категории, которую я написал для UIPageViewController
:
@implementation UIPageViewController (Additions)
- (void)setViewControllers:(NSArray *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction invalidateCache:(BOOL)invalidateCache animated:(BOOL)animated completion:(void (^)(BOOL finished))completion {
NSArray *vcs = viewControllers;
__weak UIPageViewController *mySelf = self;
if (invalidateCache && self.transitionStyle == UIPageViewControllerTransitionStyleScroll) {
UIViewController *neighborViewController = (direction == UIPageViewControllerNavigationDirectionForward
? [self.dataSource pageViewController:self viewControllerBeforeViewController:viewControllers[0]]
: [self.dataSource pageViewController:self viewControllerAfterViewController:viewControllers[0]]);
[self setViewControllers:@[neighborViewController] direction:direction animated:NO completion:^(BOOL finished) {
[mySelf setViewControllers:vcs direction:direction animated:animated completion:completion];
}];
}
else {
[mySelf setViewControllers:vcs direction:direction animated:animated completion:completion];
}
}
@end
Что вы можете сделать, чтобы проверить это, создайте новое "приложение на основе страницы" и добавьте кнопку "goto", которая будет "прыгать" на определенный календарный месяц, а затем жест. Обязательно установите стиль перехода для прокрутки.
Ответ 3
Я использую эту функцию (я всегда в ландшафтном, 2-страничном режиме)
-(void) flipToPage:(NSString * )index {
int x = [index intValue];
LeafletPageContentViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSUInteger retreivedIndex = [self indexOfViewController:theCurrentViewController];
LeafletPageContentViewController *firstViewController = [self viewControllerAtIndex:x];
LeafletPageContentViewController *secondViewController = [self viewControllerAtIndex:x+1 ];
NSArray *viewControllers = nil;
viewControllers = [NSArray arrayWithObjects:firstViewController, secondViewController, nil];
if (retreivedIndex < x){
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
} else {
if (retreivedIndex > x ){
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:NULL];
}
}
}
Ответ 4
Вот мое решение Swift, которое будет использоваться для подклассов UIPageViewController
:
Предположим, вы храните массив viewControllers в viewControllerArray
и текущий индекс страницы в updateCurrentPageIndex
.
private func slideToPage(index: Int, completion: (() -> Void)?) {
let tempIndex = currentPageIndex
if currentPageIndex < index {
for var i = tempIndex+1; i <= index; i++ {
self.setViewControllers([viewControllerArray[i]], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: {[weak self] (complete: Bool) -> Void in
if (complete) {
self?.updateCurrentPageIndex(i-1)
completion?()
}
})
}
}
else if currentPageIndex > index {
for var i = tempIndex - 1; i >= index; i-- {
self.setViewControllers([viewControllerArray[i]], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: {[weak self] (complete: Bool) -> Void in
if complete {
self?.updateCurrentPageIndex(i+1)
completion?()
}
})
}
}
}
Ответ 5
Быстрая версия ответа djibouti33:
weak var pvcw = pageViewController
pageViewController!.setViewControllers([page], direction: UIPageViewControllerNavigationDirection.Forward, animated: true) { _ in
if let pvcs = pvcw {
dispatch_async(dispatch_get_main_queue(), {
pvcs.setViewControllers([page], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
})
}
}
Ответ 6
Важно отметить, что это уже не так в iOS 10, и вам больше не нужно использовать принятое решение для ответа. Просто продолжайте, как всегда.
Ответ 7
Я могу подтвердить эту проблему и что это происходит только при использовании UIPageViewControllerTransitionStyleScroll
, а не UIPageViewControllerTransitionStylePageCurl
.
Обходной путь: Сделайте цикл и вызовите UIPageViewController setViewControllers
для каждого поворота страницы, пока не достигнете нужной страницы.
Это позволяет синхронизировать внутренний индекс источника данных в UIPageViewController.
Ответ 8
Это только решение
-(void)buyAction
{
isFromBuy = YES;
APPChildViewController *initialViewController = [self viewControllerAtIndex:4];
viewControllers = [NSArray arrayWithObject:initialViewController];
[self.pageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
-(NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
if (isFromBuy) {
isFromBuy = NO;
return 5;
}
return 0;
}
Ответ 9
У меня был другой подход, возможно, если ваши страницы будут обновлены после init:
Когда выбрана ручная страница, я обновляю флаг
- (void)scrollToPage:(NSInteger)page animated:(BOOL)animated
{
if (page != self.currentPage) {
[self setViewControllers:@[[self viewControllerForPage:page]]
direction:(page > self.currentPage ?
UIPageViewControllerNavigationDirectionForward :
UIPageViewControllerNavigationDirectionReverse)
animated:animated
completion:nil];
self.currentPage = page;
self.forceReloadNextPage = YES; // to override view controller automatic page cache
}
}
- (ScheduleViewController *)viewControllerForPage:(NSInteger)page
{
CustomViewController * scheduleViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"CustomViewController"];
scheduleViewController.view.tag = page; // keep track of pages using view.tag property
scheduleViewController.data = [self dataForPage:page];
if (self.currentViewController)
scheduleViewController.calendarLayoutHourHeight = self.currentViewController.calendarLayoutHourHeight;
return scheduleViewController;
}
а затем принудительно перезагрузите следующую страницу с помощью правильных данных:
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers
{
CustomViewController * nextViewController = [pendingViewControllers lastObject];
// When manual scrolling occurs, the next page is loaded from UIPageViewController cache
// and must be refreshed
if (self.forceReloadNextPage) {
// calculate the direction of the scroll to know if to load next or previous page
NSUInteger page = self.currentPage + 1;
if (self.currentPage > nextViewController.view.tag) page = self.currentPage - 1;
nextViewController.data = [self dataForPage:page];
self.forceReloadNextPage = NO;
}
}
Ответ 10
Если вам не нужно анимировать новую страницу, как я этого не сделал, следующий код работал у меня, вызвал "Value Changed" в раскадровке. Вместо изменения между контроллерами представлений я изменяю данные, связанные с текущим контроллером.
- (IBAction)pageControlCurrentPageDidChange:(id)sender
{
self.currentIndex = self.pageControl.currentPage;
MYViewController *currentPageViewController = (MYViewController *)self.pageViewController.viewControllers.firstObject;
currentPageViewController.pageData = [self.pageDataSource dataForPage:self.currentIndex];
[currentPageViewController updateDisplay];
}
currentIndex есть, поэтому я могу обновить текущую страницу pageControl, когда я прокручиваю между страницами.
pageDataSource dataForPage: возвращает массив объектов данных, которые отображаются на страницах.
Ответ 11
Вот обновленная версия для быстрой версии
Ответ 12
Я слишком долго боролся с этой проблемой. Для меня у меня был загрузчик UIPageViewController (я называл его PageController) из раскадровки, и на нем я добавляю UIViewController 'ContentVC'.
Я разрешаю ContentVC заботиться о данных, которые будут загружены в область содержимого, и пусть PageController позаботится о скользящих /goto/PageIndicator обновлениях. ContentVC имеет ivar CurrentPageIndex и отправляет это значение в PageController, поэтому PageController знает, на какой странице он включен. В моем файле .m, который имеет PageController, у меня есть эти два метода.
Обратите внимание, что я использовал set для 0, и поэтому каждый раз, когда перезагрузка VVV, он переходит на первую страницу, которую я не хочу, [self viewControllerAtIndex: 0].
- (void)setPageForward
{
ContentVC *FirstVC = [self viewControllerAtIndex:[CurrentPageIndex integerValue]];
NSArray *viewControllers = @[FirstVC];
[PageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
Этот второй метод - метод PageViewController DataSource. presentationIndexForPageViewController установит выделенную точку на нужную страницу (желаемая страница). Обратите внимание: если мы вернем 0, индикатор страницы выделит первую точку, которая указывает первую страницу, и мы этого не хотим.
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
return [CurrentPageIndex integerValue];
}
Ответ 13
let orderedViewControllers = [UIViewController(),UIViewController(), UIViewController()]
let pageViewController = UIPageViewController()
let pageControl = UIPageControl()
func jump(to: Int, completion: @escaping (_ vc: UIViewController?) -> Void){
guard orderedViewControllers.count > to else{
//index of bounds
return
}
let toVC = orderedViewControllers[to]
var direction: UIPageViewController.NavigationDirection = .forward
if pageControl.currentPage < to {
direction = .forward;
} else {
direction = .reverse;
}
pageViewController.setViewControllers([toVC], direction: direction, animated: true) { _ in
DispatchQueue.main.async {
self.pageViewController.setViewControllers([toVC], direction: direction, animated: false){ _ in
self.pageControl.currentPage = to
completion(toVC)
}
}
}
}
ИСПОЛЬЗОВАНИЕ:
self.jump(to: 5) { (vc) in
// you can do anything for new vc.
}