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];
}
Edit Примечание: я добавил упрощенный код для метода удаления очереди. Даже с этой кощунственной реализацией у меня точно такая же проблема с порядком страниц.
инициализации все работает, как ожидалось. Дополнительных подкачки все работает отлично. Проблема в том, что если я когда-нибудь позвоню jumpToDay опять же, порядок перемешивается.
если пользователь находится на день -5 и переходит к Дню 1, прокрутка влево покажет день -5 снова вместо соответствующего дня 0. Это, кажется, имеет какое-то отношение к тому, как UIPageViewController сохраняет ссылки на близлежащие страницы, но я не удается найти ссылку на метод, который заставил бы его обновить свой кэш.
какие идеи?
12 ответов:
Программирование iOS6, Мэтт Нойбург документирует эту точную проблему, и я на самом деле обнаружил, что его решение чувствует себя немного лучше, чем в настоящее время принятый ответ. Это решение, которое отлично работает, имеет отрицательный побочный эффект анимации изображения до/после, а затем резко заменяет эту страницу на нужную страницу. Я чувствовал, что это был странный пользовательский опыт, и решение Мэтта заботится об этом.
__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]; }); }];
поэтому я столкнулся с той же проблемой, что и вы, где мне нужно было "перейти" на страницу, а затем обнаружил, что "порядок испорчен", когда я жестом вернул страницу. Насколько я могу судить, контроллер вида страницы определенно кэширует контроллеры вида, и когда вы "прыгаете" на страницу, вам нужно указать направление: вперед или назад. Затем предполагается, что новый контроллер вида является "соседом" предыдущего контроллера вида и, следовательно, автоматически представляет предыдущий вид контроллер, когда вы жест назад. Я обнаружил, что это происходит только тогда, когда вы используете
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", которая будет "прыгать" в определенный календарный месяц, а затем жест назад. Обязательно установите стиль перехода для прокрутки.
Я использую эту функцию (я всегда в ландшафтном, 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]; } } }
вот мое быстрое решение, которое будет использоваться для подклассов
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?() } }) } } }
важно отметить, что это больше не так в iOS 10, и вам больше не нужно использовать принятое решение ответа. Просто продолжайте, как всегда.
Я могу подтвердить эту проблему, и что это происходит только при использовании
UIPageViewControllerTransitionStyleScroll, а неUIPageViewControllerTransitionStylePageCurl.решение: сделать цикл и позвонить
UIPageViewController setViewControllersдля каждого поворота страницы, пока вы не достигнете нужной страницы.это сохраняет внутренний индекс источника данных в UIPageViewController в синхронизации.
Swift версия ответа джибути33:
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) }) } }
Это только решение
-(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; }
у меня был другой подход, должно быть возможно, если ваши страницы предназначены для обновления после 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; } }
Если вам не нужно анимировать на новую страницу, как я не сделал, следующий код работал для меня, вызвал "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 есть, поэтому я могу обновить currentPage pageControl, когда я провожу между страницами.
pageDataSource dataForPage: возвращает массив объектов данных, которые отображаются страницы.
Я сам долго боролся с этим вопросом. Для меня у меня был uipageviewcontroller (я назвал его PageController) загрузка из раскадровки и на нем я добавляю UIViewController 'ContentVC'.
Я позволяю ContentVC заботиться о данных, которые будут загружены в область содержимого, и пусть PageController заботится о обновлениях sliding/goto/PageIndicator. ContentVC имеет ivar CurrentPageIndex и отправляет это значение в PageController, чтобы PageController знал, какая это страница на. В моем.m файл, который имеет PageController у меня есть эти два метода.
обратите внимание, что я использовал значение 0, и поэтому каждый раз, когда PageVC перезагружается, он переходит на первую страницу, которую я не хочу, [self viewControllerAtIndex:0].
- (void)setPageForward { ContentVC *FirstVC = [self viewControllerAtIndex:[CurrentPageIndex integerValue]]; NSArray *viewControllers = @[FirstVC]; [PageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; }этот второй метод является методом источника данных PageViewController. presentationIndexForPageViewController установит выделенную точку на правую страницу (страницу, которую вы хотите). Обратите внимание, что если мы вернем 0 здесь индикатор страницы будет выделять первую точку что указывает на первую страницу, и мы этого не хотим.
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController { return [CurrentPageIndex integerValue]; }
вот последняя версия Swift 3+ ответ от @djibouti33 с очищенным синтаксисом.
weak var weakPageVc = pageVc pageVc.setViewControllers([page], direction: .forward, animated: true) { finished in guard let pageVc = weakPageVc else { return } DispatchQueue.main.async { pageVc.setViewControllers([page], direction: .forward, animated: false) } }
Comments