Guide de mise en page du contrôleur de navigation non respecté avec une transition personnalisée

Version courte:

J’ai un problème avec le guide de disposition top de la mise en page automatique lorsqu’il est utilisé avec une transition personnalisée et UINavigationController dans iOS7. Plus précisément, la contrainte entre le guide de disposition du haut et la vue du texte n’est pas respectée. Quelqu’un at-il rencontré ce problème?


Version longue:

J’ai une scène qui définit sans ambiguïté des contraintes (c.-à-d. Haut, bas, gauche et droite) qui donnent une vue comme celle-ci:

droite

Mais lorsque je l’utilise avec une transition personnalisée sur le contrôleur de navigation, la première contrainte sur le guide de mise en page supérieur semble désactivée et le rendu est le suivant, comme si le guide de disposition du haut était en haut de l’écran du contrôleur de navigation:

faux

Il semblerait que le “top layout guide” avec le contrôleur de navigation devienne confus lors de l’utilisation de la transition personnalisée. Les autres contraintes sont appliquées correctement. Et si je fais pivoter l’appareil et que je le fait pivoter à nouveau, tout est soudainement rendu correctement, il ne semble donc pas que les contraintes ne soient pas définies correctement. De même, lorsque je désactive ma transition personnalisée, les vues s’affichent correctement.

Cela dit, _autolayoutTrace signale que les objects UILayoutGuide souffrent de UILayoutGuide , lorsque je cours:

 (lldb) po [[UIWindow keyWindow] _autolayoutTrace] 

Mais ces guides de mise en page sont toujours signalés comme ambigus chaque fois que je les regarde, même si je me suis assuré qu’il n’y avait pas de contraintes manquantes (j’ai fait la sélection habituelle du contrôleur de vues et choisi “Ajouter les contraintes manquantes pour le contrôleur de vue”). des contrôles et faire la même chose pour eux).

En ce qui concerne la précision de la transition, j’ai spécifié un object conforme à UIViewControllerAnimatedTransitioning dans la méthode animationControllerForOperation :

 - (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController*)fromVC toViewController:(UIViewController*)toVC { if (operation == UINavigationControllerOperationPush) return [[PushAnimator alloc] init]; return nil; } 

Et

 @implementation PushAnimator - (NSTimeInterval)transitionDuration:(id )transitionContext { return 0.5; } - (void)animateTransition:(id)transitionContext { UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] addSubview:toViewController.view]; CGFloat width = fromViewController.view.frame.size.width; toViewController.view.transform = CGAffineTransformMakeTranslation(width, 0); [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.transform = CGAffineTransformMakeTranslation(-width / 2.0, 0); toViewController.view.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) { fromViewController.view.transform = CGAffineTransformIdentity; [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end 

J’ai également effectué un rendu de ce qui précède, en définissant le frame de la vue plutôt que la transform , avec le même résultat.

J’ai également essayé manuellement de m’assurer que les contraintes sont layoutIfNeeded appelant layoutIfNeeded . J’ai aussi essayé setNeedsUpdateConstraints , setNeedsLayout , etc.

En bout de ligne, quelqu’un a-t-il réussi à épouser une transition personnalisée du contrôleur de navigation avec des contraintes qui utilisent le guide de présentation du haut?

J’ai résolu ce problème en fixant la contrainte de hauteur du topLayoutGuide . L’ajustement de edgesForExtendedLayout n’était pas une option pour moi, car j’avais besoin de la vue de destination pour masquer la barre de navigation, mais aussi pour pouvoir mettre en forme les sous- topLayoutGuide aide de topLayoutGuide .

L’inspection directe des contraintes en jeu montre topLayoutGuide ajoute une contrainte de hauteur au topLayoutGuide avec une valeur égale à la hauteur de la barre de navigation du contrôleur de navigation. Sauf que dans iOS 7, l’utilisation d’une transition d’animation personnalisée laisse la contrainte avec une hauteur de 0. Ils ont corrigé cela dans iOS 8.

C’est la solution que j’ai trouvée pour corriger la contrainte (c’est dans Swift mais l’équivalent devrait fonctionner dans Obj-C). J’ai testé le fonctionnement sur iOS 7 et 8.

 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view let destinationVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! destinationVC.view.frame = transitionContext.finalFrameForViewController(destinationVC) let container = transitionContext.containerView() container.addSubview(destinationVC.view) // Custom transitions break topLayoutGuide in iOS 7, fix its constraint if let navController = destinationVC.navigationController { for constraint in destinationVC.view.constraints() as [NSLayoutConstraint] { if constraint.firstItem === destinationVC.topLayoutGuide && constraint.firstAtsortingbute == .Height && constraint.secondItem == nil && constraint.constant == 0 { constraint.constant = navController.navigationBar.frame.height } } } // Perform your transition animation here ... } 

Géré pour résoudre mon problème en ajoutant cette ligne:

 toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; 

À:

 - (void)animateTransition:(id)transitionContext fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC fromView:(UIView *)fromView toView:(UIView *)toView { // Add the toView to the container UIView* containerView = [transitionContext containerView]; [containerView addSubview:toView]; [containerView sendSubviewToBack:toView]; // animate toVC.view.frame = [transitionContext finalFrameForViewController:toVC]; NSTimeInterval duration = [self transitionDuration:transitionContext]; [UIView animateWithDuration:duration animations:^{ fromView.alpha = 0.0; } completion:^(BOOL finished) { if ([transitionContext transitionWasCancelled]) { fromView.alpha = 1.0; } else { // reset from- view to its original state [fromView removeFromSuperview]; fromView.alpha = 1.0; } [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } 

De la documentation Apple pour [finalFrameForViewController]: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewControllerContextTransitioning_protocol/#//apple_ref/occ/intfm/UIViewControllerContextTransitioning/finalFrameForViewController :

J’ai lutté avec le même problème. Le fait de mettre ceci dans le viewDidLoad de mon toViewController m’a vraiment aidé:

 self.edgesForExtendedLayout = UIRectEdgeNone; 

Cela n’a pas résolu tous mes problèmes et je cherche toujours une meilleure approche, mais cela a certainement facilité les choses.

FYI, j’ai fini par utiliser une variante de la réponse d’ Alex , en modifiant par programmation la constante de hauteur du guide de disposition supérieur dans la méthode animateTransition . Je ne fais que poster ceci pour partager le rendu Objective-C (et éliminer le test constant == 0 ).

 CGFloat navigationBarHeight = toViewController.navigationController.navigationBar.frame.size.height; for (NSLayoutConstraint *constraint in toViewController.view.constraints) { if (constraint.firstItem == toViewController.topLayoutGuide && constraint.firstAtsortingbute == NSLayoutAtsortingbuteHeight && constraint.secondItem == nil && constraint.constant < navigationBarHeight) { constraint.constant += navigationBarHeight; } } 

Merci Alex.

Il suffit de mettre le code suivant à viewDidLoad

 self.extendedLayoutIncludesOpaqueBars = YES; 

Comme @Rob l’a mentionné, topLayoutGuide n’est pas fiable lors de l’utilisation de transitions personnalisées dans UINavigationController . J’ai contourné ce problème en utilisant mon propre guide de mise en page. Vous pouvez voir le code en action dans ce projet de démonstration . Points forts:

Une catégorie pour les guides de disposition personnalisés:

 @implementation UIViewController (hp_layoutGuideFix) - (BOOL)hp_usesTopLayoutGuideInConstraints { return NO; } - (id)hp_topLayoutGuide { id object = objc_getAssociatedObject(self, @selector(hp_topLayoutGuide)); return object ? : self.topLayoutGuide; } - (void)setHp_topLayoutGuide:(id)hp_topLayoutGuide { HPLayoutSupport *object = objc_getAssociatedObject(self, @selector(hp_topLayoutGuide)); if (object != nil && self.hp_usesTopLayoutGuideInConstraints) { [object removeFromSuperview]; } HPLayoutSupport *layoutGuide = [[HPLayoutSupport alloc] initWithLength:hp_topLayoutGuide.length]; if (self.hp_usesTopLayoutGuideInConstraints) { [self.view addSubview:layoutGuide]; } objc_setAssociatedObject(self, @selector(hp_topLayoutGuide), layoutGuide, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end 

HPLayoutSupport est la classe qui servira de guide de mise en page. Il doit s’agir d’une sous-classe UIView pour éviter les plantages (je me demande pourquoi cela ne fait pas partie de l’interface UILayoutSupport ).

 @implementation HPLayoutSupport { CGFloat _length; } - (id)initWithLength:(CGFloat)length { self = [super init]; if (self) { self.translatesAutoresizingMaskIntoConstraints = NO; self.userInteractionEnabled = NO; _length = length; } return self; } - (CGSize)insortingnsicContentSize { return CGSizeMake(1, _length); } - (CGFloat)length { return _length; } @end 

UINavigationControllerDelegate est le responsable de la “fixation” du guide de mise en page avant la transition:

 - (id )navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { toVC.hp_topLayoutGuide = fromVC.hp_topLayoutGuide; id  animator; // Initialise animator return animator; } 

Enfin, UIViewController utilise hp_topLayoutGuide au lieu de topLayoutGuide dans les contraintes et l’indique en hp_usesTopLayoutGuideInConstraints :

 - (void)updateViewConstraints { [super updateViewConstraints]; id topLayoutGuide = self.hp_topLayoutGuide; // Example constraint NSDictionary *views = NSDictionaryOfVariableBindings(_imageView, _dateLabel, topLayoutGuide); NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topLayoutGuide][_imageView(240)]-8-[_dateLabel]" options:NSLayoutFormatAlignAllCenterX mesortingcs:nil views:views]; [self.view addConstraints:constraints]; } - (BOOL)hp_usesTopLayoutGuideInConstraints { return YES; } 

J’espère que cela aide.

j’ai trouvé le chemin Décochez d’abord la propriété “Extend Edges” du contrôleur. après cette barre de navigation obtenir une couleur sombre. Ajoutez une vue au contrôleur et définissez LayoutConstraint -100 en haut et en bas. Ensuite, faites la propriété clipsubview de la vue no (pour l’effet transculentbar de navigation). Mon anglais mauvais pour ça. 🙂

J’ai eu le même problème, j’ai fini par implémenter ma propre vue du guide topLayout et lui ai imposé des contraintes plutôt que de topLayoutGuide. Pas idéal Seulement la poster ici au cas où quelqu’un serait coincé et à la recherche d’une solution rapide de piratage http://www.github.com/ssortingngcode86/SCTopLayoutGuide

essayez:

 self.edgesforextendedlayout=UIRectEdgeNone 

Ou définissez simplement la barre de navigation opaque et définissez l’image d’arrière-plan ou la couleur d’arrière-plan sur la barre de navigation

Voici la solution simple que j’utilise et qui fonctionne très bien pour moi: pendant la phase d’installation de - (void)animateTransition:(id)transitionContext , définissez manuellement votre “from” et “to” viewController.view.frame.origin .y = navigationController.navigationBar.frame.size.height. Vos vues de mise en page automatique se positionneront verticalement comme prévu.

Moins le pseudo-code (par exemple, vous avez probablement votre propre façon de déterminer si un appareil exécute iOS7), voici à quoi ressemble ma méthode:

 - (void)animateTransition:(id)transitionContext { UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *container = [transitionContext containerView]; CGAffineTransform destinationTransform; UIViewController *targetVC; CGFloat adjustmentForIOS7AutoLayoutBug = 0.0f; // We're doing a view controller POP if(self.isViewControllerPop) { targetVC = fromViewController; [container insertSubview:toViewController.view belowSubview:fromViewController.view]; // Only need this auto layout hack in iOS7; it's fixed in iOS8 if(_device_is_running_iOS7_) { adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height; [toViewController.view setFrameOriginY:adjustmentForIOS7AutoLayoutBug]; } destinationTransform = CGAffineTransformMakeTranslation(fromViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug); } // We're doing a view controller PUSH else { targetVC = toViewController; [container addSubview:toViewController.view]; // Only need this auto layout hack in iOS7; it's fixed in iOS8 if(_device_is_running_iOS7_) { adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height; } toViewController.view.transform = CGAffineTransformMakeTranslation(toViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug); destinationTransform = CGAffineTransformMakeTranslation(0.0f,adjustmentForIOS7AutoLayoutBug); } [UIView animateWithDuration:_animation_duration_ delay:_animation_delay_if_you_need_one_ options:([transitionContext isInteractive] ? UIViewAnimationOptionCurveLinear : UIViewAnimationOptionCurveEaseOut) animations:^(void) { targetVC.view.transform = destinationTransform; } completion:^(BOOL finished) { [transitionContext completeTransition:([transitionContext transitionWasCancelled] ? NO : YES)]; }]; } 

Quelques exemples de bonus à propos de cet exemple:

  • Pour les poussées du contrôleur de vue, cette transition personnalisée fait glisser le viewView to ViewController.view par-dessus le délocking de ViewController.view. Pour les pops, fromViewController.view glisse vers la droite et révèle un viewViewController.view immobile en dessous. Dans l’ensemble, c’est juste une torsion subtile sur la transition de contrôleur de vue iOS7 +.
  • Le bloc d’achèvement [UIView animateWithDuration:...] indique la manière correcte de gérer les transitions personnalisées terminées et annulées. Cette minuscule friandise était un moment classique de gifle; J’espère que cela aidera quelqu’un d’autre.

Enfin, je tiens à préciser que, pour autant que je sache, il s’agit d’un problème lié à iOS7 qui a été résolu dans iOS8: ma transition de contrôleur de vue personnalisée qui est rompue dans iOS7 fonctionne parfaitement dans iOS8 sans modification. Cela étant dit, vous devriez vérifier que c’est ce que vous voyez également, et si c’est le cas, exécutez uniquement le correctif sur les périphériques exécutant iOS7.x. Comme vous pouvez le voir dans l’exemple de code ci-dessus, la valeur d’ajustement y est 0.0f sauf si le périphérique exécute iOS7.x.

J’ai rencontré ce même problème, mais sans utiliser UINavigationController et juste positionner une vue du topLayoutGuide . La mise en page serait correcte lorsqu’elle s’affiche pour la première fois, une transition s’effectue vers une autre vue, puis à la sortie et en revenant à la première vue, la mise en page est interrompue car topLayoutGuide ne sera plus là.

J’ai résolu ce problème en capturant les encarts de la zone de sécurité avant la transition, puis en les réimplémentant, non pas en ajustant mes contraintes, mais en les définissant dans les EnsemblesAboutres additionalSafeAreaInsets de viewController.

J’ai trouvé que cette solution fonctionnait bien car je n’ai pas besoin d’ajuster mon code de mise en page et de rechercher à travers des contraintes et je ne peux que réimplémenter l’espace existant auparavant. Cela peut être plus difficile si vous utilisez la propriété additionalSafeAreaInsets .

Exemple

J’ai ajouté une variable à mon transitionManager pour capturer les encarts sécurisés existants lors de la création de transitionManager.

 class MyTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate { private var presenting = true private var container:UIView? private var safeInsets:UIEdgeInsets? ... 

Ensuite, pendant la transition entrante, je sauvegarde ces encarts.

  let toView = viewControllers.to.view let fromView = viewControllers.from.view if #available(iOS 11.0, *) { safeInsets = toView.safeAreaInsets } 

Dans le cas de l’iPhone X, cela ressemble à UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)

Maintenant, à la sortie, les encarts sur cette même vue depuis l’entrée seront .zero . Nous ajoutons donc nos encarts capturés aux additionalSafeAreaInsets sur le viewController, ce qui les placera dans notre vue pour nous et mettra à jour la mise en page. Une fois notre animation terminée, nous redéfinissons le .zero additionalSafeAreaInsets à .zero .

  if #available(iOS 11.0, *) { if safeInsets != nil { viewControllers.to.additionalSafeAreaInsets = safeInsets! } } ...then in the animation completion block if #available(iOS 11.0, *) { if self.safeInsets != nil { viewControllers.to.additionalSafeAreaInsets = .zero } } transitionContext.completeTransition(true) 

Dans le storyboard, ajoutez une autre contrainte verticale au sumt de la vue principale. J’ai le même problème, mais l’ajout de cette contrainte m’aide à éviter les contraintes manuelles. Voir la capture d’écran ici lien

Une autre solution consiste à calculer la trame toVC … quelque chose comme ceci:

 float y = toVC.navigationController.navigationBar.frame.origin.y + toVC.navigationController.navigationBar.frame.size.height; toVC.view.frame = CGRectMake(0, y, toVC.view.frame.size.width, toVC.view.frame.size.height - y); 

Faites-moi savoir si vous avez trouvé une meilleure solution. J’ai aussi lutté contre ce problème et j’ai eu des idées précédentes.