UipageViewController: devuelva la vista visible actual
-
28-10-2019 - |
Pregunta
¿Cómo sabe cuál es la página/vista actual que se muestra dentro de un UIPageViewController
?
He anulado el viewDidAppear
método de las opiniones de mi hijo, para que envíen una identificación a la vista de los padres en su viewDidAppear
método.
Sin embargo, el problema es este: no puedo usar esa identificación de manera confiable como ID para la página mostrada. Porque si el usuario gira la página pero a mitad de camino decide detener el giro y volver a poner la página, viewDidAppear
ya lo habrán llamado. (La vista es visible detrás de la página curlada).
Tal vez solo debería cambiar a una nueva ID si la vista actual desaparece. Pero me pregunto si no hay una manera más simple de devolver la vista que actualmente es visible.
Solución
Debe realizar un seguimiento manualmente de la página actual. El método delegado pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
le dirá cuándo actualizar esa variable. El último argumento del método transitionCompleted:
Puede decirle si un usuario completó una transición de turno de página o no.
Otros consejos
A partir de iOS 6 he encontrado que el viewControllers
Propiedad de UIPageViewController se actualiza constantemente para que siempre mantenga el controlador único que representa la página actual y nada más. Por lo tanto, puede acceder a la página actual llamando viewControllers[0]
(Suponiendo que solo muestre un controlador de vista a la vez).
La matriz ViewController solo se actualiza una vez que la página se "bloquea" en su lugar, por lo que si un usuario decide revelar parcialmente la página siguiente, no se convierte en la página "actual" a menos que complete la transición.
Si desea realizar un seguimiento de los "números de página", asigne a sus controladores de vista un valor de índice a medida que los crea a través de los métodos UIPageViewController DataSource.
Así por ejemplo:
-(void)autoAdvance
{
UIViewController *currentVC = self.viewControllers[0];
NSUInteger currentIndex = [myViewControllers indexOfObject:currentVC];
if ( currentIndex >= (myViewControllers.count-1) ) return;
[self setViewControllers:@[myViewControllers[ currentIndex+1 ]]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}
-(NSInteger)presentationIndexForPageViewController:
(UIPageViewController *)pageViewController
{
// return 0;
UIViewController *currentVC = self.viewControllers[0];
NSUInteger currentIndex = [myViewControllers indexOfObject:currentVC];
return currentIndex;
}
Pero Tenga en cuenta los comentarios que esto no es confiable.
Desafortunadamente, todos los métodos anteriores no me ayudaron. Sin embargo, he encontrado la solución usando etiquetas. Puede ser que no sea el mejor, pero funciona y espero que ayude a alguien:
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if (completed) {
int currentIndex = ((UIViewController *)self.pageViewController.viewControllers.firstObject).view.tag;
self.pageControl.currentPage = currentIndex;
}
}
En Swift: (gracias a @Jessy)
func pageViewController(pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool)
{
guard completed else { return }
self.pageControl.currentPage = pageViewController.viewControllers!.first!.view.tag
}
Ejemplo: esencia
Construyendo la respuesta de Ole ...
Así es como implementé los 4 métodos para rastrear la página actual y actualizar el indicador de página al índice correcto:
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController{
return (NSInteger)[self.model count];
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController{
return (NSInteger)self.currentIndex;
}
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers{
SJJeanViewController* controller = [pendingViewControllers firstObject];
self.nextIndex = [self indexOfViewController:controller];
}
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if(completed){
self.currentIndex = self.nextIndex;
}
self.nextIndex = 0;
}
La solución a continuación funcionó para mí.
Apple podría evitar mucha molestia al hacer que la paginación de la vista de desplazamiento UipageViewController nativa sea más configurable. Tuve que recurrir a superposición de un nuevo UIView y UIPageControl solo porque la paginación nativa UIPageViewController no admitirá un fondo transparente o reposicionamiento dentro del marco de la vista.
- (void)pageViewController:(UIPageViewController *)pvc didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if (!completed)
{
return;
}
NSUInteger currentIndex = [[self.pageViewController.viewControllers lastObject] index];
self.pageControl.currentPage = currentIndex;
}
Swift 4
Sin código innecesario. 3 formas de hacerlo. Usando UipageViewControllerDelegate método.
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard completed else { return }
// using content viewcontroller's index
guard let index = (pageViewController.viewControllers?.first as? ContentViewController)?.index else { return }
// using viewcontroller's view tag
guard let index = pageViewController.viewControllers?.first?.view.tag else { return }
// switch on viewcontroller
guard let vc = pageViewController.viewControllers?.first else { return }
let index: Int
switch vc {
case is FirstViewController:
index = 0
case is SecondViewController:
index = 1
default:
index = 2
}
}
Estoy realizando un seguimiento del índice de página utilizando una función pequeña y especificando PageIndex como nsinteger estático.
-(void) setPageIndex
{
DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
pageIndex = [self.modelController indexOfViewController:theCurrentViewController];
}
y llamar [self setPageIndex];
Dentro de la función especificada por OLE y también después de detectar el cambio en la orientación.
Primero usé la solución de Corey, pero no estaba funcionando en iOS5 y luego terminé usando,
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if(completed) {
_currentViewController = [pageViewController.viewControllers lastObject];
}
}
Intentó cambiar a través de diferentes páginas y funciona bien por ahora.
Desafortunadamente, nada arriba funciona para mí.
Tengo dos controladores de vista y cuando le desprendí ligeramente (alrededor de 20px) la última vista hacia atrás, desencadena al delegado:
pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
y decir que la página actual (índice) es 0
Cuál está mal.
Usar delegado dentro de Child ViewController algo así:
- (void)ViewController:(id)VC didShowWithIndex:(long)page;
// and a property
@property (nonatomic) NSInteger index;
que se activa por dentro viewDidAppear
me gusta:
- (void)viewDidAppear:(BOOL)animated
{
...
[self.delegate ViewController:self didShowWithIndex:self.index];
}
Trabajó para mi.
Esto funciona para mí de manera confiable
Tengo un UIPageController personalizado. Esta pageController.CurrentPage se actualiza desde el UIViewController mostrado en ViewWillapPear
var delegate: PageViewControllerUpdateCurrentPageNumberDelegate?
init(delegate: PageViewControllerUpdateCurrentPageNumberDelegate ){
self.delegate = delegate
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(animated: Bool) {
if delegate != nil {
self.delegate!.upateCurrentPageNumber(0) //(0) is the pageNumber corresponding to the displayed controller
}
}
//In the pageViewController
protocol PageViewControllerUpdateCurrentPageNumberDelegate {
func upateCurrentPageNumber(currentPageIndex: Int)
}
create the view display controllers initializing with the delegate
orderedViewControllers = {
return [
IntroductionFirstPageViewController(delegate: self),
IntroductionSecondPageViewController(delegate: self),
IntroductionThirdPageViewController(delegate: self)
]
}()
the function implementing the protocol
func upateCurrentPageNumber(currentPageIndex: Int){
pageControl.currentPage = currentPageIndex
}
He estado usando view.tag
Desde hace un tiempo, tratar de realizar un seguimiento de la página actual fue demasiado complicado.
En este código el índice se almacena dentro del tag
propiedad de cada view
y se usa para obtener el siguiente VC o anterior. Usando este método también es posible crear un desplazamiento infinito. Consulte el comentario en el código para ver esta solución también:
extension MyPageViewController: UIPageViewControllerDataSource {
func viewControllerWithIndex(var index: Int) -> UIViewController! {
let myViewController = storyboard?.instantiateViewControllerWithIdentifier("MyViewController") as MyViewController
if let endIndex = records?.endIndex {
if index < 0 || index >= endIndex { return nil }
// Instead, We can normalize the index to be cyclical to create infinite scrolling
// if index < 0 { index += endIndex }
// index %= endIndex
}
myViewController.view.tag = index
myViewController.record = records?[index]
return myViewController
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let index = viewController.view?.tag ?? 0
return viewControllerWithIndex(index + 1)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let index = viewController.view?.tag ?? 0
return viewControllerWithIndex(index - 1)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return records?.count ?? 0
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return (pageViewController.viewControllers.first as? UIViewController)?.view.tag ?? 0
}
}
Gracias por su respuesta, chicos, enfrenté un problema similar, tuve que almacenar el índice. Modifico ligeramente mi código, lo pego a continuación:
- (MenuListViewController *)viewControllerAtIndex:(NSInteger)index {
if (_menues.count < 1)
return nil;
// MenuListViewController *childViewController = [MenuListViewController initWithSecondSetFakeItems];
MenuListViewController *childViewController = self.menues[index];
childViewController.index = index;
return childViewController;
}
#pragma mark - Page View Controller Data Source
- (void)pageViewController:(UIPageViewController *)pageViewController
didFinishAnimating:(BOOL)finished
previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers
transitionCompleted:(BOOL)completed{
if (completed) {
NSUInteger currentIndex = ((MenuListViewController *)self.pageController.viewControllers.firstObject).index;
NSLog(@"index %lu", (unsigned long)currentIndex);
}
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [(MenuListViewController *)viewController index];
if (index == 0)
return nil;
index --;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [(MenuListViewController *)viewController index];
index ++;
if (index == _menues.count)
return nil;
return [self viewControllerAtIndex:index];
}
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
NSLog(@"Current Page = %@", pageViewController.viewControllers);
UIViewController *currentView = [pageViewController.viewControllers objectAtIndex:0];
if ([currentView isKindOfClass:[FirstPageViewController class]]) {
NSLog(@"First View");
}
else if([currentView isKindOfClass:[SecondPageViewController class]]) {
NSLog(@"Second View");
}
else if([currentView isKindOfClass:[ThirdViewController class]]) {
NSLog(@"Third View");
}
}
//pageViewController.viewControllers always return current visible View ViewController
A continuación, el código de demostración (en Swift 2) que demuestra cómo se hace esto mediante la implementación de un tutorial de atletismo de imagen simple. Comentarios en el código en sí:
import UIKit
/*
VCTutorialImagePage represents one page show inside the UIPageViewController.
You should create this page in your interfacebuilder file:
- create a new view controller
- set its class to VCTutorialImagePage
- sets its storyboard identifier to "VCTutorialImagePage" (needed for the loadView function)
- put an imageView on it and set the contraints (I guess to top/bottom/left/right all to zero from the superview)
- connect it to the "imageView" outlet
*/
class VCTutorialImagePage : UIViewController {
//image to display, configure this in interface builder
@IBOutlet weak var imageView: UIImageView!
//index of this page
var pageIndex : Int = 0
//loads a new view via the storyboard identifier
static func loadView(pageIndex : Int, image : UIImage) -> VCTutorialImagePage {
let storyboard = UIStoryboard(name: storyBoardHome, bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("VCTutorialImagePage") as! VCTutorialImagePage
vc.imageView.image = image
vc.pageIndex = pageIndex
return vc
}
}
/*
VCTutorialImageSwiper takes an array of images (= its model) and displays a UIPageViewController
where each page is a VCTutorialImagePage that displays an image. It lets you swipe throught the
images and will do a round-robbin : when you swipe past the last image it will jump back to the
first one (and the other way arround).
In this process, it keeps track of the current displayed page index
*/
class VCTutorialImageSwiper: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
//our model = images we are showing
let tutorialImages : [UIImage] = [UIImage(named: "image1")!, UIImage(named: "image2")!,UIImage(named: "image3")!,UIImage(named: "image4")!]
//page currently being viewed
private var currentPageIndex : Int = 0 {
didSet {
currentPageIndex=cap(currentPageIndex)
}
}
//next page index, temp var for keeping track of the current page
private var nextPageIndex : Int = 0
//Mark: - life cylce
override func viewDidLoad() {
super.viewDidLoad()
//setup page vc
dataSource=self
delegate=self
setViewControllers([pageForindex(0)!], direction: .Forward, animated: false, completion: nil)
}
//Mark: - helper functions
func cap(pageIndex : Int) -> Int{
if pageIndex > (tutorialImages.count - 1) {
return 0
}
if pageIndex < 0 {
return (tutorialImages.count - 1)
}
return pageIndex
}
func carrouselJump() {
currentPageIndex++
setViewControllers([self.pageForindex(currentPageIndex)!], direction: .Forward, animated: true, completion: nil)
}
func pageForindex(pageIndex : Int) -> UIViewController? {
guard (pageIndex < tutorialImages.count) && (pageIndex>=0) else { return nil }
return VCTutorialImagePage.loadView(pageIndex, image: tutorialImages[pageIndex])
}
func indexForPage(vc : UIViewController) -> Int {
guard let vc = vc as? VCTutorialImagePage else {
preconditionFailure("VCPagImageSlidesTutorial page is not a VCTutorialImagePage")
}
return vc.pageIndex
}
//Mark: - UIPageView delegate/datasource
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
return pageForindex(cap(indexForPage(viewController)+1))
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
return pageForindex(cap(indexForPage(viewController)-1))
}
func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [UIViewController]) {
nextPageIndex = indexForPage(pendingViewControllers.first!)
}
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if !finished { return }
currentPageIndex = nextPageIndex
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return tutorialImages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return currentPageIndex
}
}
Tengo una matriz ViewControllers, que muestro en el UipageViewController.
extension MyViewController: UIPageViewControllerDataSource {
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return self.viewControllers.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return self.currentPageIndex
}
}
extension MyViewController: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if !completed { return }
guard let viewController = previousViewControllers.last, let index = indexOf(viewController: viewController) else {
return
}
self.currentPageIndex = index
}
fileprivate func indexOf(viewController: UIViewController) -> Int? {
let index = self.viewControllers.index(of: viewController)
return index
}
}
Lo importante a tener en cuenta aquí es que el setViewControllers método de UipageViewController no da ninguna devolución de llamada delegada. Las devoluciones de llamada delegadas solo representan acciones táctiles del usuario en el UipageViewController.
Esta es la solución que se me ocurrió:
class DefaultUIPageViewControllerDelegate: NSObject, UIPageViewControllerDelegate {
// MARK: Public values
var didTransitionToViewControllerCallback: ((UIViewController) -> Void)?
// MARK: Private values
private var viewControllerToTransitionTo: UIViewController!
// MARK: Methods
func pageViewController(
_ pageViewController: UIPageViewController,
willTransitionTo pendingViewControllers: [UIViewController]
) {
viewControllerToTransitionTo = pendingViewControllers.last!
}
func pageViewController(
_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool
) {
didTransitionToViewControllerCallback?(viewControllerToTransitionTo)
}
}
Uso:
let pageViewController = UIPageViewController()
let delegate = DefaultUIPageViewControllerDelegate()
delegate.didTransitionToViewControllerCallback = {
pageViewController.title = $0.title
}
pageViewController.title = viewControllers.first?.title
pageViewController.delegate = delegate
Asegúrese de establecer el título inicial
La forma más sencilla de abordar este IMHO es usar el pageControl para almacenar el resultado potencial de la transición y luego revertir si la transición se canceló. Esto significa que el control de la página cambia tan pronto como el usuario comienza a deslizar, lo cual está bien por mí. Esto requiere que tenga su propia variedad de UiviewControllers (en este ejemplo llamado allViewControllers
)
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
if let index = self.allViewControllers.index(of: pendingViewControllers[0]) {
self.pageControl.currentPage = index
}
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if !completed, let previousIndex = self.allViewControllers.index(of: previousViewControllers[0]) {
self.pageControl.currentPage = previousIndex
}
}
Realizar un seguimiento de nuestro número de página actual en los métodos delegados:
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController
Utilizamos un GetVC Helper para actualizar nuestro número de página y devolver el siguiente/anterior VC. Este es un buen lugar para hacer esto porque GetVC solo se llama si hay un VC siguiente o anterior.
///Helper
func getVC(for pgNum: Int) -> BasePageVC {
let vc = storyboard?.instantiateViewController(withIdentifier: "page"+String(pgNum)) as! BasePageVC
vc.pageNumber = pgNum
self.currentPage = pgNum
vc.data = self.data
return vc
}
/// Next Page
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? BasePageVC else {
fatalError("Vc is not BasePageViewController in viewControllerAfter")
}
let nextPage = vc.pageNumber! + 1
guard nextPage < self.data.pages.count - 1 else { return nil }
return getVC(for: nextPage)
}
/// Previous Page
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? BasePageVC else {
fatalError("Vc is not BasePageViewController in viewControllerAfter")
}
let prevPage = vc.pageNumber! - 1
guard prevPage >= 0 else { return nil }
return getVC(for: prevPage)
}
¿Qué tal pedir un viewController
directamente del UIPageViewController
(Versión Swift 4):
fileprivate weak var currentlyPresentedVC: UIViewController?
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
currentlyPresentedVC = pageViewController.viewControllers?.first
}
O, si solo necesita el controlador de vista presentado actualmente en algún momento, simplemente use pageViewController.viewControllers?.first
En ese tiempo.
UIViewController *viewController = [pageViewController.viewControllers objectAtIndex:0];
NSUInteger currentIndex = [(ViewController*) viewController indexNumber];
Devolverá el índice de página actual. y debe usar este código en la función delegada de UipageViewController (didfinishanimating).