web-dev-qa-db-fra.com

Suppression d'un contrôleur de vue de UIPageViewController

Il est étrange qu'il n'y ait pas de moyen simple de faire cela. Considérez le scénario suivant:

  1. Vous avez un contrôleur de vue de page avec 1 page.
  2. Ajoutez une autre page (total 2) et faites-la défiler.
  3. Ce que je veux, c'est: lorsque l'utilisateur revient à la première page, la 2ème page est maintenant supprimée et désallouée, et l'utilisateur ne peut plus revenir à cette page.

J'ai essayé de supprimer le contrôleur de vue en tant que contrôleur de vue enfant une fois la transition terminée, mais cela me permet toujours de revenir à la page vide (cela ne "redimensionne" pas la vue de page)

Est-ce que ce que je veux faire est possible?

44
Snowman

En conclusion de la bonne réponse de Matt Mc, la méthode suivante pourrait être ajoutée à une sous-classe de UIPageViewController, autorisant ainsi l'utilisation de setViewControllers: direction: animée: complétion: comme elle était destinée à être utilisée si le bogue n'était pas présent.

- (void) setViewControllers:(NSArray*)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^)(BOOL))completion {

    if (!animated) {
        [super setViewControllers:viewControllers direction:direction animated:NO completion:completion];
        return;
    }

    [super setViewControllers:viewControllers direction:direction animated:YES completion:^(BOOL finished){

        if (finished) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [super setViewControllers:viewControllers direction:direction animated:NO completion:completion];
            });
        } else {
            if (completion != NULL) {
                completion(finished);
            }
        }
    }];
}

Maintenant, appelez simplement setViewControllers: direction: animated: complétion: sur la classe/les sous-classes implémentant cette méthode, et cela devrait fonctionner comme prévu.

9
Lukas Kalinski

maq a raison. Si vous utilisez la transition de défilement, la suppression d'un contrôleur de vue enfant de UIPageViewController n'empêche pas la "page" supprimée de revenir à l'écran si l'utilisateur y accède. Si cela vous intéresse, voici comment j'ai supprimé le contrôleur de vue enfant de UIPageViewController. 

// deleteVC is a child view controller of the UIPageViewController
[deleteVC willMoveToParentViewController:nil];
[deleteVC.view removeFromSuperview];
[deleteVC removeFromParentViewController]; 

Le contrôleur de vue deleteVC est supprimé de la propriété childViewControllers de UIPageViewController, mais reste affiché à l'écran si l'utilisateur y accède. 

En attendant qu'une personne plus intelligente que moi trouve une solution élégante, voici une solution (c'est un hack - vous devez donc vous demander si vous avez vraiment besoin de supprimer des pages d'un UIPageViewController). 

Ces instructions supposent qu'une seule page est affichée à la fois. 

Une fois que l'utilisateur a appuyé sur un bouton lui indiquant qu'il souhaite supprimer la page, accédez à la page suivante ou précédente à l'aide de la méthode setViewControllers: direction: animated: completion:. Bien entendu, vous devez ensuite supprimer le contenu de la page de votre modèle de données. 

Ensuite (et voici le hack), créez et configurez un tout nouveau UIPageViewController et chargez-le au premier plan (c'est-à-dire devant l'autre UIPageViewController). Assurez-vous que le nouveau UIPageViewController commence par afficher exactement la même page que celle précédemment affichée. Votre nouveau UIPageViewController va récupérer les nouveaux contrôleurs de vue de la source de données. 

Enfin, déchargez et détruisez le UIPageViewController qui se trouve en arrière-plan. 

Quoi qu'il en soit, maq a posé une très bonne question. Malheureusement, je n'ai pas assez de points de réputation pour pouvoir voter. Ah, osez rêver ... un jour, j'aurai 15 points de réputation. 

6
bilobatum

Je vais mettre cette réponse ici juste pour ma propre référence future et si cela peut aider quelqu'un - ce que j'ai fini par faire était:

  1. Supprimer la page et passer à la page suivante
  2. Dans le bloc d'achèvement de setViewControllers, j'ai créé/initié un nouveau UIPageViewController avec les données modifiées (élément supprimé) et je l'ai poussé sans animer, donc rien ne change à l'écran (mon UIPageViewController est contenu dans un UINavigationController)
  3. Après avoir poussé le nouveau UIPageViewController, obtenez une copie du tableau viewControllers du UINavigationController, supprimez l'avant-dernier contrôleur de vue (l'ancien UIPageViewController).
  4. Il n'y a pas d'étape 4 - c'est fait!
5
Jai Govindani

Pour améliorer cela. Vous devez détecter si pageView défile ou non avant setViewControllers.

var isScrolling = false

func viewDidLoad() {
...

  for v in view.subviews{
    if v.isKindOfClass(UIScrollView) {
      (v as! UIScrollView).delegate = self
    }
  }
}

func scrollViewWillBeginDragging(scrollView: UIScrollView){
    isScrolling = true
}

func scrollViewDidEndDecelerating(scrollView: UIScrollView){
    isScrolling = false
}

func jumpToVC{
    if isScrolling {  //you should not jump out when scrolling
        return
    }
    setViewControllers([vc], direction:direction, animated:true, completion:{[unowned self] (succ) -> Void in
        if succ {
            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                self.setViewControllers([vc], direction:direction, animated:false, completion:nil)
            })
        }
    })
}
3
TonnyTao

Ce problème existe lorsque vous essayez de modifier viewControllers lors d'une animation de geste de balayage entre viewControllers. Pour résoudre ce problème, j'ai créé un simple drapeau à découvrir lorsque le contrôleur de vue de page est en cours de transition. 

- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers {
    self.pageViewControllerTransitionInProgress = YES;
}

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
        self.pageViewControllerTransitionInProgress = NO;
}

Et quand j'essaye de changer la vue, je vérifie s'il y a une transition en cours.

- (void)setCurrentPage:(NSString *)newCurrentPageId animated:(BOOL)animated {

    if (self.pageViewControllerTransitionInProgress) {
        return;
    }

    [self.pageContentViewController setViewControllers:@[pageDetails] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {
    }

}
2
mientus

Je viens d'apprendre cela moi-même, alors prenez avec un grain de sel, mais de ce que je comprends, vous devez changer la source de données du contrôleur de pageview, pas supprimer le contrôleur de vue. Le nombre de pages affichées dans un contrôleur pageview est déterminé par sa source de données, et non par les contrôleurs de vue.

1
user1459524
NSMutableArray *mArray = [[NSMutableArray alloc] initWithArray:self.userArray];
[mArray removeObject:userToBlock];
self.userArray = mArray;

UIViewController *startingViewController = [self viewControllerAtIndex:atIndex-1];
NSArray *viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];

Je n'ai pas vraiment eu le temps de lire les commentaires ci-dessus, mais cela a fonctionné pour moi. Fondamentalement, je supprime les données (dans mon cas, un utilisateur), puis je passe à la page précédente. Fonctionne comme un charme. J'espère que cela aidera ceux qui recherchent une solution rapide.

0
Ibdakine

Dans une situation similaire, je souhaitais que l'utilisateur puisse "appuyer et supprimer" n'importe quelle page de UIPageViewController. Après avoir joué un peu avec, j'ai trouvé une solution plus simple que celles décrites ci-dessus:

  1. Capturez l'index de page de la "page en train de mourir". 
  2. Vérifiez si la "page en train de mourir" est la dernière page .
    • Ceci est important car s'il s'agit de la dernière page, nous devons faire défiler vers la gauche (si nous avons les pages "A B C" et supprimer C, nous allons faire défiler jusqu'à B).
    • Si ce n'est pas la dernière page, nous allons défiler vers la droite (si nous avons les pages "A B C" et supprimer B, nous allons faire défiler jusqu'à C).
  3. Faites un saut temporaire vers un endroit "sûr" (idéalement le dernier). Utilisez animation: NO pour que cela se produise instantanément.

    UIViewController *jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
    [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
    
  4. Supprimer la page sélectionnée du modèle/source de données.

  5. Maintenant, si c'est la dernière page:

    • Ajustez votre modèle pour sélectionner la page de gauche.
    • Obtenez le contrôleur de vue sur la gauche, notez que CECI IS est le même que celui que vous avez récupéré à l'étape 3.
    • Il est important de le faire APRÈS avoir supprimé la page de la source de données, car celle-ci sera actualisée.
    • Allez-y, cette fois avec animée: OUI. 

      jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
      [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
      
  6. Dans le cas où ce n'était PAS la dernière page:

    • Ajustez votre modèle pour sélectionner la page à droite.
    • Obtenez le contrôleur de vue à droite, notez qu'il ne s'agit pas de celui que vous avez récupéré à l'étape 3. Dans mon cas, vous verrez qu'il s'agit de celui qui se trouve sur dyingPageIndex, car la page en train de mourir a déjà été supprimée du modèle.
    • Là encore, il est important de procéder APRÈS avoir supprimé la page de la source de données, car celle-ci sera actualisée.
    • Allez-y, cette fois avec animée: OUI.

      jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex;
      [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
      
  7. C'est tout! Cela fonctionne bien dans XCode 6.1.1.

Code complet ci-dessous. Ce code est dans mon UIPageViewController et est appelé par un délégué de la page à supprimer. Dans mon cas, la suppression de la première page n'était pas autorisée car elle contenait des éléments différents du reste des pages. Bien sûr, vous devez substituer: 

  • YourUIViewController: avec la classe de vos pages individuelles. 
  • YourTotalNumberOfPagesFromModel: avec le nombre total de pages de votre modèle 
  • [YourModel deletePage:] avec le code pour supprimer la page mourante de votre modèle

    - (void)deleteAViewController:(id)sender {
        YourUIViewController *dyingGroup = (YourUIViewController *)sender;
        NSUInteger dyingPageIndex = dyingGroup.pageIndex;
        // Check to see if we are in the last page as it's a special case.
        BOOL isTheLastPage = (dyingPageIndex >= YourTotalNumberOfPagesFromModel.count);
        // Make a temporary jump back - make sure to use animated:NO to have it jump instantly
        UIViewController *jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
        [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
        // Now delete the selected group from the model, setting the target
        [YourModel deletePage:dyingPageIndex];
        if (isTheLastPage) {
            // Now jump to the definitive controller. In this case, it's the same one, we're just reloading it to refresh the data source.
            // This time we're using animated:YES
            jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
            [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
        } else {
            // Now jump to the definitive controller. This reloads the data source. This time we're using animated:YES
            jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex];
            [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
        }
    }
    
0
Roberto Moctezuma

En fait, je pense avoir résolu le problème. Voici ce qui se passait dans mon application:

  1. L'instance de sous-classe UIViewController a été supprimée de UIPageViewController en la supprimant du modèle et en définissant la propriété viewControllers de UIPageViewController sur un autre ViewController. C'est assez pour faire le travail. Pas besoin de faire un code de confinement du contrôleur sur ce contrôleur
  2. Le ViewController avait disparu, mais je pouvais quand même y accéder en balayant dans mon cas. 
  3. Mon problème était celui-ci. J'ajoutais un identifiant de geste personnalisé à ViewController affiché par UIPageViewController. Ce dispositif de reconnaissance était maintenu en tant que propriété puissante sur le contrôleur qui appartenait également à UIPageViewController.
  4. CORRECTIF: avant de perdre l'accès au ViewController, je me suis assuré de bien nettoyer toute la mémoire qu'il utilise (dealloc) et de supprimer le programme de reconnaissance de geste
  5. Votre kilométrage peut varier, mais je ne vois pas l'intérêt pour cette solution de se tromper, lorsque quelque chose ne fonctionne pas, je soupçonne d'abord mon code :)
0
mdomans

Il l'a fait ce qui suit dans Swift 3.0 

fileprivate var isAnimated:Bool = false

override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewControllerNavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {

        if self.isAnimated {
            delay(0.5, closure: { 
                self.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
            })
        }else {
                super.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
        }

    }


extension SliderViewController:UIPageViewControllerDelegate {

    func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
        self.isAnimated = true
    }

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        self.isAnimated = finished
    }
}

Et voici la fonction delay 

func delay(_ delay:Double, closure:@escaping ()->()) {
    DispatchQueue.main.asyncAfter(
        deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
0
Eike