web-dev-qa-db-fra.com

Sur iOS 7, pousser un contrôleur avec une barre d'outils laisse un espace inutilisable s'il est finalement contenu dans un contrôleur de barre d'onglets

Dans mon application iOS, rootViewController de ma fenêtre est un contrôleur de barre d'onglets avec une hiérarchie comme celle-ci:

  • UITabBarController
    • UINavigationController 1
      • FirstContentController
    • UINavigationController 2
      • ...
    • UINavigationController 3
      • ...
    • ...

Lorsque l'utilisateur tapote une certaine ligne sur FirstContentController, une instance de SecondController sera poussée sur son contrôleur de navigation. SecondContentController définit hidesBottomBarWhenPushed sur YES dans sa méthode init et définit self.navigationController.toolbarHidden à NO dans viewWillAppear:.

Dans iOS 6, l'utilisateur tapait la ligne dans FirstController et SecondController était poussé sur le contrôleur de navigation. Parce qu'il a hidesBottomBarWhenPushed défini, il masquerait la barre d'onglets et, au moment où l'animation de transition serait terminée, SecondController serait à l'écran avec sa barre d'outils visible.

Cependant, lors du test sous iOS 7, le comportement de hidesBottomBarWhenPushed semble avoir changé. Ce que je vois maintenant, c'est:

  • la barre d'onglets se cache, comme prévu
  • la barre d'outils apparaît, comme prévu
  • un espace inutilisable d'une hauteur exacte de 49 pixels (la hauteur de la barre d'onglets) apparaît entre la barre d'outils et la vue du contenu

L'écart est complètement inutilisable - il ne répond pas aux touches et si je mets clipsToBounds sur YES dans la vue principale, rien n'y dessine. Après beaucoup de débogage et d'examen des hiérarchies de sous-vues, il semble que le mécanisme de redimensionnement automatique d'iOS redimensionne la vue du contrôleur de vue à une hauteur de 411 (sur l'iPhone 5). Il devrait être de 460 pour atteindre la barre d'outils, mais le système de disposition semble inclure une barre d'onglets "fantôme" de 49 pixels.

Ce problème se produit uniquement si le contrôleur de vue a un contrôleur de barre d'onglets comme un si ses conteneurs parents.

Sur iOS 7, comment puis-je faire disparaître la barre d'onglets et faire glisser une barre d'outils de manière transparente lorsqu'un nouveau contrôleur est poussé, et laisser la vue occuper tout l'espace entre l'élément de navigation et la barre d'outils?

MISE À JOUR

Après une enquête plus approfondie, cela ne se produit que si edgesForExtendedLayout de SecondController est défini sur UIRectEdgeNone. Cependant, à moins que je ne définisse cette propriété sur UIRectEdgeNone, le cadre de la vue est trop long et s'étend sous la barre d'outils, où il ne peut pas être vu ou interagi avec.

39
Bill

Décochez "Masquer les barres inférieures sur Push" et définissez vos contraintes automatiques comme s'il y avait une barre d'onglets. Ensuite, dans "ViewDidLoad" du contrôleur que vous souhaitez masquer la barre d'onglets système, mettez le code suivant.

[self.tabBarController.tabBar setFrame:CGRectZero];

Cela garantit que la barre d'onglets accepte toujours l'interaction de l'utilisateur mais n'est pas visible pour les utilisateurs. (d'autres alternatives telles que la définition de 0 alpha ou caché rendront la barre d'onglets inutile).

8
Ah Ryun Moon

J'ai trouvé que l'ajout des 2 lignes de code suivantes dans viewDidLoad de SecondViewController (où vous souhaitez masquer TabBar mais afficher la barre d'outils) résout le problème.

self.extendedLayoutIncludesOpaqueBars = YES;
self.edgesForExtendedLayout = UIRectEdgeBottom;

Mon viewDidLoad de SecondViewController est le suivant:

- (void)viewDidLoad {
    [super viewDidLoad];
    // These 2 lines made the difference
    self.extendedLayoutIncludesOpaqueBars = YES;
    self.edgesForExtendedLayout = UIRectEdgeBottom;

    // The usual configuration
    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
    self.navigationController.navigationBar.translucent = NO;

    self.navigationController.toolbarHidden = NO;
    self.navigationController.toolbar.barStyle = UIBarStyleBlack;
    self.navigationController.toolbar.translucent = NO;
    .
    .
}

Mais vous devez corriger le cadre de la vue manuellement car cela entraîne une taille (320x504). Ce qui signifie qu'il s'étend même derrière la barre d'outils. Si cela ne vous préoccupe pas, cette solution devrait fonctionner.

19
srik

Vous n'aimerez pas cette réponse Ce n'est pas la réponse que vous voulez, mais après quelques recherches sur le masquage de la barre d'onglets dans iOS7, ma conclusion est: ne le faites pas!

Les barres d'onglets n'ont jamais été conçues pour être cachées - après tout, pourquoi avoir un UITabBarController si vous voulez masquer la barre d'onglets. Le hidesBottomBarWhenPushed sur les contrôleurs de vue sert à masquer la barre inférieure d'un contrôleur de navigation, pas les barres d'onglets. De la documentation:

Un contrôleur de vue ajouté en tant qu'enfant de n contrôleur de navigation peut afficher une barre d'outils en option en bas de l'écran. La valeur de cette propriété sur le contrôleur de vue le plus haut détermine si la barre d'outils est visible. Si la valeur de cette propriété est YES, la barre d'outils est masquée. Si la valeur de cette propriété est NO, la barre est visible.

De plus, vous êtes averti de ne pas modifier directement l'objet de la barre d'onglets. Encore une fois, à partir de la documentation:

Vous ne devez jamais tenter de manipuler l'objet UITabBar lui-même stocké dans cette propriété.

C'est exactement ce que vous faites lorsque vous le définissez sur caché.

Dans iOS6, cela a fonctionné, mais maintenant dans iOS7, cela ne fonctionne pas. Et il semble très enclin à le cacher. Lorsque vous parvenez enfin à le masquer, si l'application passe en arrière-plan et revient, la logique de mise en page d'Apple remplace vos modifications.

Ma suggestion est d'afficher vos données de façon modale. Dans iOS7, vous pouvez créer des transitions personnalisées, donc s'il est important pour vous d'avoir une transition Push, vous pouvez la recréer vous-même, bien que ce soit un peu trop. La transition modale normale est quelque chose que les utilisateurs connaissent, et correspond en fait mieux à ce cas que Push qui cache la barre d'onglets.


Une autre solution consiste à utiliser une barre d'outils au lieu d'une barre d'onglets. Si vous utilisez la barre d'outils du contrôleur de navigation pour vos onglets, vous pouvez ensuite utiliser hidesBottomBarWhenPushed selon vos besoins et cela vous donnera le comportement que vous attendez.

17
Leo Natan

C'est un bug dans iOS 7 UIKit en raison de cette combinaison particulière de:

  • UITabBarController
  • hidesBottomBarWhenPushed = OUI
  • bordsForExtendedLayout = UIRectEdgeNone
  • Barre d'outils UINavigationController

Vous devez signaler un bogue avec Apple et inclure votre exemple de code.

Pour contourner le bogue, vous devez supprimer l'une de ces quatre conditions. Deux options probables:

  1. Corrigez la disposition de votre "deuxième" contrôleur de vue afin qu'il fonctionne correctement lorsque edgesForExtendedLayout est défini sur UIRectEdgeAll. Cela pourrait être aussi simple que de définir le contentInset sur une vue de défilement.

  2. N'utilisez pas la barre d'outils intégrée d'UINavigationController. Au lieu de cela, créez une instance UIToolBar distincte et ajoutez-la manuellement à la vue de votre deuxième contrôleur de vue.

5
Darren

Vous devez définir le tabBar du TabBarController sur hidden et votre vue devrait avoir autosizing réglé sur une hauteur flexible.

Avec ce code ça marche:

@implementation SecondController

-(id)init
{
    if( (self = [super init]) )
    {
    }

    return self;
}

- (void)viewDidLoad;
{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight;
    self.tabBarController.tabBar.hidden = YES;
}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // will log a height of 411, instead of the desired 460
    NSLog(@"frame: %@", NSStringFromCGRect(self.view.frame));
}

@end

Ou, si vous voulez utiliser la méthode hidesBottomBarWhenPushed, vous devez le faire avant vous poussez le contrôleur de vue évidemment:

-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
    SecondController* controller = [[SecondController alloc] init];
    controller.hidesBottomBarWhenPushed = YES;

    [self.navigationController pushViewController:controller animated:YES];
}

Si vous utilisez la deuxième méthode, votre méthode viewDidLoad peut se débarrasser de la méthode de la hauteur flexible ainsi que de tabBarHidden:

- (void)viewDidLoad;
{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    self.edgesForExtendedLayout = UIRectEdgeNone;
}

Voir le résultat:

enter image description here

2
Alexander

La clé de cette énigme est que la taille de navigationcontroller.view.frame ne change pas. Aller de Batkin's Gist ici est un Gist of my own .

FirstViewController.m

#import "FirstController.h"
#import "SecondController.h"

@implementation FirstController

-(id)init
{
    if( (self = [super init]) )
    {
        self.tabBarItem.title = @"Foo";
        self.tabBarItem.image = [UIImage imageNamed:@"Tab Icon.png"];
    }

    return self;
}

-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
    return 1;
}

-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
    UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];

    cell.textLabel.text = @"Click";

    return cell;
}

-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
    SecondController* controller = [[SecondController alloc] init];
    self.tabBarController.tabBar.hidden = YES;
    [self.navigationController pushViewController:controller animated:YES];
}

@end

SecondViewController.m

#import "SecondController.h"

@implementation SecondController

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.view.backgroundColor = [UIColor redColor];
    self.view.clipsToBounds = YES;

    /* ENTER VORTEX OF DESPAIR */

    // without this, there's no gap, but the view continues under the tool 
    // bar; with it, I get the 49-pixel gap thats making my life miserable

    self.edgesForExtendedLayout = UIRectEdgeNone;

    //this resizes the navigation controller to fill the void left by the tab bar.
    CGRect newFrame = self.navigationController.view.frame;
    newFrame.size.height = newFrame.size.height + 49;
    self.navigationController.view.frame = newFrame;

    /* EXIT VORTEX OF DESPAIR */

    self.navigationController.toolbarItems = @[
                                               [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:nil action:nil]
                                               ];


}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    self.navigationController.toolbarHidden = NO;

    // will log a height of 411, instead of the desired 460
    NSLog(@"frame: %@", NSStringFromCGRect(self.view.frame));
    NSLog(@"frame: %@", NSStringFromCGRect(self.navigationController.view.frame));
}

-(void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    self.tabBarController.tabBar.hidden = NO;
    self.navigationController.toolbarHidden = YES;

    //this resizes the navigation controller back to normal.
    CGRect newFrame = self.navigationController.view.frame;
    newFrame.size.height = newFrame.size.height - 49;
    self.navigationController.view.frame = newFrame;

    //this is optional and resizes the view to fill the void left by the missing toolbar.
    CGRect newViewFrame = self.view.frame;
    newViewFrame.size.height = newViewFrame.size.height + 49;
    self.view.frame = newViewFrame;

}

@end
2
Hackmodford

Si vous utilisez la mise en page automatique, assurez-vous d'épingler la vue dans sa vue d'ensemble au lieu du guide de mise en page supérieur ou du guide de mise en page inférieur.

2
tounaobun

Vous mentionnez que vous pouvez résoudre ce problème en ne touchant pas le edgesForExtendedLayout. Y a-t-il une raison nécessaire pour que le contenu/les contrôles du contrôleur de vue soient contenus dans la vue racine du contrôleur de vue poussé? Vous pouvez envisager de tout emballer dans une vue qui est le premier et le seul enfant de la vue principale. Ajustez ensuite le cadre de cette vue dans le viewDidLayoutSubviews du contrôleur de vue poussé pour éviter d'avoir du contenu en permanence sous la barre d'outils en utilisant le top/bottomLayoutGuide du contrôleur de vue.

0
Felix Lamouroux

Je gère manuellement le masquage/affichage de la barre d'onglets inférieure avec l'animation de fondu en

 ...

[self.tabBarController.tabBar setHidden:NO];

[self.tabBarController.tabBar setAlpha:0.1];
[UIView animateWithDuration:0.2 animations:^{
    [self.tabBarController.tabBar setAlpha:1.0];
}];
 ...

La barre d'outils inférieure sur SecondVC a été ajoutée dans IB. Aucun problème jusqu'ici. Utilisation de Storyboard.

0
dklt

Cela m'aide: Choisissez votre contrôleur de vue dans le storyboard -> Aller aux propriétés -> Décochez "Ajuster les encarts de la vue de défilement"

0
George

J'ai construit un nouveau projet en utilisant votre Gist, et j'ai enfermé l'UITabBarController dans un UINavigationController:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    UITabBarController* tabController = [[UITabBarController alloc] init];

    tabController.viewControllers = @[
                                      [[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc] init]],
                                      [[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc] init]]
                                      ];

    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:tabController];
    [navController setNavigationBarHidden:YES];
    self.window.rootViewController = navController;

    return YES;
}

Et pour montrer le SecondViewController, voici ce que j'ai fait:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    SecondViewController* controller = [[SecondViewController alloc] init];

    // Reaching the UITabBarViewController's parent navigationController
    [self.parentViewController.navigationController pushViewController:controller animated:YES];
}

Enfin, dans le secondViewController:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.view.backgroundColor = [UIColor redColor];
    self.view.clipsToBounds = YES;

    // The following line only works in iOS7
    if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
        self.edgesForExtendedLayout = UIRectEdgeNone;
    }

    [self.navigationItem setRightBarButtonItem:[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:nil action:nil]];

    UIBarButtonItem * logoutButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:nil action:nil];
    NSMutableArray * arr = [NSMutableArray arrayWithObjects:logoutButton, nil];
    [self setToolbarItems:arr animated:YES];


    [self.navigationController setNavigationBarHidden:NO animated:YES];
    [self.navigationController setToolbarHidden:NO animated:YES];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [self.navigationController setNavigationBarHidden:YES animated:YES];
    [self.navigationController setToolbarHidden:YES animated:YES];
}

Voici à quoi cela ressemble: 3rd tentative

EDIT: Changé l'exemple et changé la capture d'écran. Rendu l'exemple iOS6 compatible.

0
Rufel

Avez-vous essayé de déplacer votre appel hidesBottomBarWhenPushed dans le viewDidLoad ou avant que le secondViewController ne soit poussé?

Avec ios7, de nombreux problèmes de synchronisation apparaissent si vous ne faites pas les appels au bon moment.

0
Geraud.ch

Je pense que vous pouvez définir les bordsForExtendedLayout de SecondController sur UIRectEdgeBottom.

0
jianpx