Comment savez-vous quelle est la page/vue actuelle affichée à l'intérieur d'une UIPageViewController
?
J'ai remplacé la méthode viewDidAppear
de mes vues enfant afin qu'elles envoient un identifiant à la vue parent dans leur méthode viewDidAppear
.
Cependant, le problème est le suivant: je ne peux pas utiliser cet identifiant de manière fiable comme identifiant pour la page affichée. parce que si l'utilisateur tourne la page mais qu'il est à mi-chemin décide de l'arrêter et de la remettre en arrière, viewDidAppear
aura déjà été appelé. (la vue est visible derrière la page courbée).
Peut-être devrais-je passer à un nouvel identifiant si la vue actuelle disparaît. Mais je me demande s’il n’existe pas un moyen plus simple de retourner la vue actuellement visible?
Vous devez suivre manuellement la page en cours. La méthode déléguée pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
vous indiquera quand mettre à jour cette variable. Le dernier argument de la méthode transitionCompleted:
peut vous indiquer si un utilisateur a terminé ou non une transition de page.
Depuis iOS 6, la propriété viewControllers
de UIPageViewController est constamment mise à jour, de sorte qu'elle contiendra toujours le seul contrôleur de vue représentant la page en cours, et rien d'autre. Ainsi, vous pouvez accéder à la page actuelle en appelant viewControllers[0]
(en supposant que vous n’affichez qu’un seul contrôleur de vue à la fois).
Le tableau viewController n'est mis à jour que lorsque la page est "verrouillée". Ainsi, si un utilisateur décide de révéler partiellement la page suivante, il ne devient pas la page "actuelle" à moins de terminer la transition.
Si vous souhaitez suivre les "numéros de page", attribuez à vos contrôleurs de vue une valeur d'index lors de leur création via les méthodes de source de données UIPageViewController.
Donc par exemple:
-(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;
}
Mais notez les commentaires que cela n’est pas fiable.
Malheureusement, toutes les méthodes ci-dessus ne m'ont pas aidé. Néanmoins, j'ai trouvé la solution en utilisant des tags. Peut-être que ce n'est pas le meilleur, mais cela fonctionne et j'espère que cela aidera quelqu'un:
- (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;
}
}
In Swift: (merci à @ 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
}
Exemple:Gist
S'appuyant sur la réponse d'Ole…
Voici comment j'ai implémenté les 4 méthodes pour suivre la page en cours et mettre à jour l'indicateur de page avec le bon index:
- (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 solution ci-dessous a fonctionné pour moi.
Apple pourrait éviter beaucoup de tracas en rendant la pagination de la vue de défilement native UIPageViewController plus configurable. J'ai dû recouvrir un nouveau UIView et UIPageControl simplement parce que la pagination UIPageViewController native ne prend pas en charge un arrière-plan transparent ou un repositionnement dans le cadre de la vue.
- (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
Pas de code inutile. 3 façons de le faire. Utiliser la méthode 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
}
}
Je garde le suivi de l'index de page en utilisant une petite fonction et en spécifiant pageIndex comme NSInteger statique.
-(void) setPageIndex
{
DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
pageIndex = [self.modelController indexOfViewController:theCurrentViewController];
}
et en appelant [self setPageIndex];
dans la fonction spécifiée par Ole et également après avoir détecté le changement d'orientation.
J'ai d'abord utilisé la solution de Corey mais cela ne fonctionnait pas sur iOS5, puis j'ai fini par utiliser,
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if(completed) {
_currentViewController = [pageViewController.viewControllers lastObject];
}
}
Il a essayé de changer de page et cela fonctionne bien pour le moment.
Cela fonctionne pour moi de manière fiable
J'ai un UIPageController personnalisé. Cette pageController.currentPage est mise à jour à partir de l'UIViewController affiché dans le 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
}
Malheureusement, rien ne marche plus pour moi.
J'ai deux contrôleurs de vue et quand je fais légèrement défiler la dernière vue vers l'arrière (environ 20 pixels), le délégué se déclenche:
pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
et en disant que la page actuelle (index) est 0
qui est faux.
Utiliser délégué dans viewController enfant quelque chose comme:
- (void)ViewController:(id)VC didShowWithIndex:(long)page;
// and a property
@property (nonatomic) NSInteger index;
qui est déclenché dans viewDidAppear
comme:
- (void)viewDidAppear:(BOOL)animated
{
...
[self.delegate ViewController:self didShowWithIndex:self.index];
}
Travaillé pour moi.
Merci pour votre réponse les gars, j'ai fait face à un problème similaire, dû stocker index. Je modifie légèrement mon code, collez-le ci-dessous:
- (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];
}
J'utilise view.tag
depuis un moment maintenant, essayer de garder une trace de la page en cours était trop compliqué.
Dans ce code, l'index est stocké dans la propriété tag
de chaque view
et est utilisé pour récupérer le VC suivant ou précédent. En utilisant cette méthode, il est également possible de créer un défilement infini. Consultez le commentaire dans le code pour afficher cette solution également:
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
}
}
Ci-dessous, le code de démonstration (dans Swift 2) qui montre comment cela est réalisé en mettant en œuvre un tutoriel simple sur les images swiper. Commentaires dans le code lui-même:
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
}
}
Garder une trace de notre numéro de page actuel dans les méthodes de délégué:
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController
Nous utilisons un helper getVC pour mettre à jour notre numéro de page et renvoyer le prochain/précédent vc. C’est un bon endroit pour faire cela car getVC n’est appelé que s’il existe un vc précédent ou suivant.
///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)
}
Voici la solution que j'ai trouvée:
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)
}
}
Usage:
let pageViewController = UIPageViewController()
let delegate = DefaultUIPageViewControllerDelegate()
delegate.didTransitionToViewControllerCallback = {
pageViewController.title = $0.title
}
pageViewController.title = viewControllers.first?.title
pageViewController.delegate = delegate
Assurez-vous de définir le titre initial
Pourquoi ne pas demander une viewController
directement à partir de la UIPageViewController
(version de Swift 4):
fileprivate weak var currentlyPresentedVC: UIViewController?
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
currentlyPresentedVC = pageViewController.viewControllers?.first
}
Ou, si vous avez simplement besoin du contrôleur de vue présenté à un moment donné, utilisez simplement pageViewController.viewControllers?.first
à ce moment-là.
- (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
J'ai un tableau viewControllers que j'affiche dans 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
}
}
Il est important de noter ici que la méthode setViewControllers de UIPageViewController ne donne aucun rappel de délégué. Les callbacks des délégués représentent uniquement les actions tactiles de l'utilisateur dans UIPageViewController.
La méthode la plus simple pour aborder cet IMHO consiste à utiliser PageControl pour stocker le résultat potentiel de la transition, puis à revenir en arrière si la transition a été annulée. Cela signifie que le contrôle de page change dès que l'utilisateur commence à balayer, ce qui me convient. Cela nécessite que vous disposiez de votre propre tableau de UIViewControllers (appelé allViewControllers
dans cet exemple).
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
}
}