iphone - tutorial - uipageviewcontroller swift 4
UIPageViewController: devuelve la vista visible actual (18)
¿Cómo sabes cuál es la página / vista actual que se muestra dentro de un UIPageViewController
?
He reemplazado el método viewDidAppear
de mis vistas secundarias, para que envíen un id a la vista primaria en su método viewDidAppear
.
Sin embargo, el problema es este: no puedo usar de manera confiable ese ID como id para la página mostrada. porque si el usuario da vuelta la página pero a la mitad decide detener el giro y vuelve a colocar la página, se habrá llamado a viewDidAppear
. (la vista es visible detrás de la página curvada).
Tal vez solo debería cambiar a una nueva identificación si la vista actual desaparece. Pero me pregunto si no hay una manera más simple de devolver la vista que está actualmente visible.
A partir de iOS 6, he encontrado que la propiedad viewControllers
de UIPageViewController se actualiza constantemente para que siempre contenga el controlador de una vista que representa la página actual, y nada más. Por lo tanto, puede acceder a la página actual llamando a 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 de origen de datos UIPageViewController.
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.
Basándose en 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;
}
Debajo del código de demostración (en Swift 2) que demuestra cómo se hace esto al implementar un simple tutorial de imágenes swiper. Comentarios en el código mismo:
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
}
}
Debe realizar un seguimiento manual de la página actual. El método de 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 giro de página o no.
Desafortunadamente, todos los métodos anteriores no me ayudaron. Sin embargo, he encontrado la solución mediante el uso de 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: gist
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
Esto funciona para mí de manera confiable
Tengo un UIPageController personalizado. Esta pageController.currentPage se actualiza desde el UIViewController que se muestra en la vista MostrarAparecer
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
}
Estoy siguiendo el índice de la página usando 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 llamando [self setPageIndex];
dentro de la función especificada por Ole y también después de detectar el cambio en la orientación.
Gracias por su respuesta chicos, me enfrenté a un problema similar, tuve que almacenar el índice. Yo modifico un poco 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];
}
He estado usando view.tag
por un tiempo, tratar de hacer un seguimiento de la página actual era demasiado complicado.
En este código, el índice se almacena dentro de la propiedad de tag
de cada view
y se usa para obtener el VC siguiente o anterior. Usando este método también es posible crear un desplazamiento infinito. Echa un vistazo al 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
}
}
La forma más sencilla de abordar este mensaje en mi humilde opinión es usar PageControl para almacenar el posible resultado de la transición y luego revertir si se canceló la transición. Esto significa que el control de página cambia tan pronto como el usuario comience a deslizar, lo cual es correcto. Esto requiere que tenga su propia matriz de UIViewControllers (en este ejemplo se llama 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
}
}
La solución a continuación funcionó para mí.
Apple podría evitar una gran cantidad de problemas al hacer que la paginación de visualización de desplazamiento UIPageViewController nativa sea más configurable. Tuve que recurrir a la superposición de un nuevo UIView y UIPageControl solo porque la paginación UIPageViewController nativa no admite 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;
}
Lamentablemente, nada de lo anterior funciona para mí.
Tengo dos controladores de vista y cuando ligeramente (alrededor de 20px) desplazo la última vista hacia atrás, desencadena el delegado:
pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
y diciendo que la página actual (índice) es 0
cual es incorrecto.
Usando delegate dentro de child viewController algo como:
- (void)ViewController:(id)VC didShowWithIndex:(long)page;
// and a property
@property (nonatomic) NSInteger index;
que se activa dentro de viewDidAppear
como:
- (void)viewDidAppear:(BOOL)animated
{
...
[self.delegate ViewController:self didShowWithIndex:self.index];
}
Trabajó para mi.
Primero usé la solución de Corey pero no funcionaba 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.
Tengo una matriz viewControllers, que se muestra en 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 método setViewControllers de UIPageViewController no proporciona ninguna devolución de llamada de delegado. Las devoluciones de llamadas de los delegados solo representan acciones táctiles del usuario en UIPageViewController .
Swift 4
Sin código innecesario 3 formas de hacerlo. Usando el método UIPageViewControllerDelegate .
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
}
}
- (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
UIViewController *viewController = [pageViewController.viewControllers objectAtIndex:0];
NSUInteger currentIndex = [(ViewController*) viewController indexNumber];
Devolverá el índice de página actual. y debe usar este código bajo la función de delegado de UIPageViewController (didFinishAnimating).