UIPageViewController, ¿cómo salgo correctamente a una página específica sin estropear el orden especificado por la fuente de datos?

He encontrado algunas preguntas sobre cómo hacer que un UIPageViewController salte a una página específica, pero he notado un problema adicional con el salto que ninguna de las respuestas parece reconocer.

Sin entrar en los detalles de mi aplicación iOS (que es similar a un calendario paginado), esto es lo que estoy experimentando. Declaro un UIPageViewController , establezco el controlador de vista actual e implemento un origen de datos.

 // 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]; } 

Editar Nota: agregué un código simplificado para el método de eliminación de secuencias. Incluso con esta implementación blasfema, tengo exactamente el mismo problema con el orden de las páginas.

La inicialización funciona como se esperaba. La paginación incremental también funciona bien también. El problema es que si vuelvo a llamar a jumpToDay , la orden se complica.

Si el usuario está en el día -5 y salta al día 1, un desplazamiento hacia la izquierda revelará el día -5 nuevamente en lugar del día apropiado 0. Esto parece tener algo que ver con cómo UIPageViewController mantiene referencias a las páginas cercanas, pero yo no puede encontrar ninguna referencia a un método que lo forzaría a actualizar su caché.

¿Algunas ideas?

La progtwigción de iOS6 , por Matt Neuburg documenta este problema exacto, y de hecho encontré que su solución se siente un poco mejor que la respuesta actualmente aceptada. Esa solución, que funciona muy bien, tiene un efecto secundario negativo de animar a la imagen antes / después, y luego reemplazarla bruscamente con la página deseada. Sentí que era una experiencia de usuario extraña, y la solución de Matt se encarga de eso.

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

Así que me encontré con el mismo problema donde tenía que poder ‘saltar’ a una página y luego encontré el ‘orden en mal estado’ cuando hice un gesto hacia atrás en una página. Por lo que he podido decir, el controlador de vista de página definitivamente está almacenando en caché los controladores de visualización y cuando ‘salta’ a una página, debe especificar la dirección: hacia adelante o hacia atrás. Luego asume que el nuevo controlador de vista es un “vecino” del controlador de vista anterior y, por lo tanto, presenta automágicamente el controlador de vista anterior cuando vuelve a hacer una seña. Descubrí que esto solo ocurre cuando estás usando UIPageViewControllerTransitionStyleScroll y no UIPageViewControllerTransitionStylePageCurl . Aparentemente, el estilo de curvatura de página no hace el mismo almacenamiento en caché ya que si ‘salta’ a una página y luego hace un gesto hacia atrás, pageViewController:viewController(Before/After)ViewController: el pageViewController:viewController(Before/After)ViewController: mensaje al origen de datos que le permite proporcionar la vista vecina correcta controlador.

Solución: Al realizar un ‘salto’ a la página, primero puede saltar a la página vecina a la página ( animated:NO ) a la que está saltando y luego en el bloque de finalización de ese salto, saltar a la página deseada. Esto actualizará el caché de modo que cuando vuelva a hacer un gesto, se muestre la página vecina correcta. El inconveniente es que deberá crear dos controladores de visualización; el que está saltando y el que debe mostrarse después de gesticular hacia atrás.

Aquí está el código de una categoría que escribí para 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 

Lo que puede hacer para probar esto es crear una nueva ‘Aplicación basada en la página’ y agregar un botón ‘ir’ que ‘saltará’ a un determinado mes calendario y luego volverá a hacer un gesto. Asegúrese de configurar el estilo de transición para desplazarse.

Uso esta función (siempre estoy en modo paisaje, de 2 páginas)

 -(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]; } } } 

Aquí está mi solución Swift para ser utilizada en las subclases de UIPageViewController :

Supongamos que almacena una matriz de viewControllers en viewControllerArray y el índice de página actual en 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?() } }) } } } 

Es importante tener en cuenta que ya no es el caso en iOS 10 y ya no tiene que usar la solución de respuesta aceptada. Simplemente continúa como siempre.

Puedo confirmar este problema, y ​​que solo ocurre cuando se utiliza UIPageViewControllerTransitionStyleScroll y no UIPageViewControllerTransitionStylePageCurl .

Solución: haga un ciclo y llame a UIPageViewController setViewControllers para cada giro de página, hasta que llegue a la página deseada.

Esto mantiene el índice interno de la fuente de datos en UIPageViewController sincronizado.

Swift version of djibouti33’s answer:

 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) }) } } 

Esta es solo una solución

 -(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; } 

Tenía un enfoque diferente, debería ser posible si sus páginas están diseñadas para actualizarse después de init:
Cuando se selecciona una página de manual, actualizo una bandera

 - (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; } 

y luego fuerce la siguiente página para volver a cargar con los datos correctos:

 - (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; } } 

Si no necesita animar a la nueva página, como yo no lo hice, el siguiente código funcionó para mí, invocó “Value Changed” en el guión gráfico. En lugar de cambiar entre los controladores de vista, cambio los datos asociados con el controlador de vista actual.

  - (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 está ahí para que pueda actualizar la página actual de pageControl cuando deslizo entre las páginas.

pageDataSource dataForPage: devuelve una matriz de objetos de datos que se muestran en las páginas.

Estuve luchando con este problema durante mucho tiempo yo mismo. Para mí, tuve una carga de UIPageViewController (la llamé PageController) desde el guión gráfico y en ella agregué un UIViewController ‘ContentVC’.

Dejo que ContentVC se encargue de los datos que se cargarán en el área de contenido y permita que PageController se encargue de las actualizaciones de deslizamiento / goto / PageIndicator. ContentVC tiene un ivar CurrentPageIndex y envía ese valor a PageController para que PageController sepa en qué página está. En mi archivo .m que tiene PageController tengo estos dos métodos.

Tenga en cuenta que he usado set en 0 y cada vez que PageVC se recarga, va a la primera página que no quiero, [self viewControllerAtIndex: 0].

 - (void)setPageForward { ContentVC *FirstVC = [self viewControllerAtIndex:[CurrentPageIndex integerValue]]; NSArray *viewControllers = @[FirstVC]; [PageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; } 

Este segundo método es el método DataSource de PageViewController. presentationIndexForPageViewController establecerá el punto resaltado en la página correcta (la página que desea). Tenga en cuenta que si devolvemos 0 aquí, el indicador de página resaltará el primer punto que indica la primera página y no queremos eso.

 - (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController { return [CurrentPageIndex integerValue]; } 

Aquí hay una versión actualizada de Swift 3+ de @djibouti33 con syntax limpiada.

 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) } }