web-dev-qa-db-fra.com

iOS 11 UINavigationBar alignement des éléments du bouton de la barre

Je mets à jour mon application vers iOS 11 et je vois des problèmes avec la barre de navigation, une partie de mes problèmes a déjà posé des questions ici, donc je ne les mentionnerai pas dans cette question.

Le problème particulier en question est que les éléments de bouton de barre de la barre de navigation sont espacés différemment. Mes éléments de bouton de barre gauche et droite principaux sont maintenant plus proches du centre horizontal de l'écran et je ne peux pas les déplacer près des bords de l'écran. Dans le passé, j'ai utilisé une sous-classe UIButton personnalisée et créé des éléments de bouton de barre avec une vue personnalisée. Les solutions d'alignement étaient alignmentRectInsets et contentEdgeInsets, maintenant je ne parvenais pas à produire les résultats attendus en utilisant cette approche.

Modifier:
J'ai retesté avec iOS 11 beta 2 et le problème persiste.

Edit 2: J'ai retesté avec iOS beta 3 et le problème persiste.

16
Georgi Boyadzhiev

Il y a un bon article à ce sujet: http://www.matrixprojects.net/p/uibarbuttonitem-ios11/

En utilisant cela, nous pouvons au moins pousser les éléments de la barre de droite vers la droite jusqu'à ce qu'il reste une marge de 8 pixels à la fin de la barre UINavigationBar.

Très bien expliqué.

4
Mayur Kothawade

Désormais, dans iOS 11, vous pouvez gérer UINavigationBarContentView pour ajuster les contraintes gauche et droite, et UIStackView pour ajuster les boutons (ou d'autres éléments).

Ceci est ma solution pour une barre de navigation avec des éléments à gauche et à droite. Il corrige également si vous avez plusieurs boutons ensemble sur un côté.

- (void) updateNavigationBar {
    for(UIView *view in self.navigationBar.subviews) {
        if ([NSStringFromClass([view class]) containsString:@"ContentView"]) {

            // Adjust left and right constraints of the content view 
            for(NSLayoutConstraint *ctr in view.constraints) {
                if(ctr.firstAttribute == NSLayoutAttributeLeading || ctr.secondAttribute == NSLayoutAttributeLeading) {
                    ctr.constant = 0.f;
                } else if(ctr.firstAttribute == NSLayoutAttributeTrailing || ctr.secondAttribute == NSLayoutAttributeTrailing) {
                    ctr.constant = 0.f;
                }
            }

            // Adjust constraints between items in stack view
            for(UIView *subview in view.subviews) {
                if([subview isKindOfClass:[UIStackView class]]) {
                    for(NSLayoutConstraint *ctr in subview.constraints) {
                        if(ctr.firstAttribute == NSLayoutAttributeWidth || ctr.secondAttribute == NSLayoutAttributeWidth) {
                            ctr.constant = 0.f;
                        }
                    }
                }
            }
        }
    }
}


Comme vous le voyez, il n'est pas nécessaire d'ajouter plus de contraintes, comme d'autres l'ont fait. Il existe déjà des contraintes définies qui peuvent donc être modifiées.

8
JonyMateos

Après environ deux jours, voici la solution la plus simple et la plus sûre que j'ai pu trouver. Cette solution ne fonctionne qu'avec des éléments de bouton de barre de vue personnalisés, dont le code est inclus. Il est important de noter que les marges gauche et droite de la barre de navigation n'ont pas changé d'iOS10 à iOS11 - elles sont toujours de 16 pixels. Une telle marge importante rend difficile d'avoir une zone de clic suffisamment grande.

Les boutons de la barre sont désormais disposés avec UIStackView. La méthode antérieure pour déplacer ces boutons plus près de la marge impliquait l'utilisation d'espaces fixes négatifs que ces vues de pile ne peuvent pas gérer.

Sous-classe UINavigationBar

FWNavigationBar.h

@interface FWNavigationBar : UINavigationBar

@end

FWNavigationBar.m

#import "FWNavigationBar.h"

@implementation FWNavigationBar

- (void)layoutSubviews {
    [super layoutSubviews];

    if (@available(iOS 11, *)) {
        self.layoutMargins = UIEdgeInsetsZero;

        for (UIView *subview in self.subviews) {
            if ([NSStringFromClass([subview class]) containsString:@"ContentView"]) {
                subview.layoutMargins = UIEdgeInsetsZero;
            }
        }
    }
}

@end

Utilisation de UINavigationController

#import "FWNavigationBar.h"

UINavigationController *controller = [UINavigationController initWithNavigationBarClass:[FWNavigationBar class] toolbarClass:nil];
[controller setViewControllers:@[rootViewController] animated:NO];

Création d'un UIBarButton

Placez ce code dans une catégorie UIBarButton ou dans le fichier que vous prévoyez d'utiliser à l'aide du bouton de barre qui renverra un élément de bouton de barre d'apparence identique à l'aide d'un bouton UIButton.

+ (UIBarButtonItem *)barButtonWithImage:(UIImage *)image target:(id)target action:(SEL)action {
   UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
   //Note: Only iOS 11 and up supports constraints on custom bar button views
   button.frame = CGRectMake(0, 0, image.size.width, image.size.height);

   button.tintColor = [UIColor lightGrayColor];//Adjust the highlight color

   [button setImage:image forState:UIControlStateNormal];
   //Tint color only applies to this image
   [button setImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateHighlighted];
   [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];

   return [[UIBarButtonItem alloc] initWithCustomView:button];
}

Réglage du bouton de barre dans votre contrôleur

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationItem.leftBarButtonItem = [UIBarButtonItem barButtonWithImage:[UIImage imageNamed:@"your_button_image"] target:self action:@selector(leftButtonPressed)];
}

Enfin, je recommanderais de laisser les marges gauche et droite à zéro et d'ajuster simplement la position du bouton dans votre image. Cela vous permettra de profiter de toute la zone cliquable jusqu'au bord de l'écran. Il en va de même pour la hauteur de votre image - assurez-vous que la hauteur est de 44 points.

7
Josh Bernfeld

J'ai remarqué un problème similaire.

J'ai signalé un Apple radar pour un problème similaire que nous avons remarqué, # 32674764 si vous voulez vous y référer, si vous créez un radar.

J'ai également créé un fil de discussion sur le forum d'Apple , mais aucun commentaire pour le moment: https://forums.developer.Apple.com/message/ 234654

4
Chris Comeau

Je suis tombé sur ceci: INavigationItem-Margin . Il fonctionne comme un charme.

4
zumph

Tiré de cette réponse : BarButtons utilise désormais Autolayout et nécessite donc des contraintes.

if #available(iOS 9.0, *) {
  cButton.widthAnchor.constraint(equalToConstant: customViewButton.width).isActive = true
  cButton.heightAnchor.constraint(equalToConstant: customViewButton.height).isActive = true
}

Objectif c

if (@available(iOS 9, *)) {
  [cButton.widthAnchor constraintEqualToConstant: standardButtonSize.width].active = YES;
  [cButton.heightAnchor constraintEqualToConstant: standardButtonSize.height].active = YES;
}
3
Dane Jordan

Solution 1: À la lumière de la réponse d'Apple selon laquelle il s'agit d'un comportement attendu, nous avons contourné le problème en supprimant la barre d'outils et en ajoutant une vue personnalisée.

Je me rends compte que cela peut ne pas être possible dans tous les cas, mais l'UIView normal est beaucoup plus facile à personnaliser à l'apparence de l'application que la barre d'outils et la barre de navigation où Apple a le contrôle du positionnement des boutons.

Au lieu de définir notre bouton personnalisé comme vue personnalisée de l'objet bouton de la barre d'interface utilisateur, nous l'avons défini comme une sous-vue des boutons d'interface utilisateur vierges dans la vue personnalisée.

Ce faisant, nous avons pu revenir au même aspect de notre application ios 10

Solution 2: Un peu désordonné, nous avons enveloppé notre bouton d'affichage personnalisé dans un bouton UIB externe afin que l'emplacement du cadre puisse être défini. Cela rend malheureusement le bord extérieur gauche du bouton inexploitable, mais corrige l'apparence de la position du bouton. Voir l'exemple:

UIButton* outerButton = [UIButton new]; //the wrapper button
BorderedButton* button = [self initBorderedButton]; //the custom button
[button setTitle:label forState:UIControlStateNormal];
[button setFrame:CGRectMake(-10, 0, [label length] * 4 + 35, 30)];
[button addTarget:controller action:@selector(popCurrentViewController) forControlEvents:UIControlEventTouchUpInside];
[outerButton addSubview:button]; //add custom button to outer wrapper button
[outerButton setFrameWidth:button.frameWidth]; //make sure title gives button appropriate space
controller.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:outerButton]; //add wrapper button to the navigation bar
controller.navigationItem.hidesBackButton = YES;

En utilisant cette méthode, nous gardons notre barre de navigation et pouvons positionner le bouton là où il est nécessaire.

Edit: Nous avons constaté que la solution 2 ne fonctionne pas sur ios 10, cela n'affectera probablement que le petit pourcentage de développeurs obligés d'être rétrocompatibles.

Solution

Ce qui nous préoccupait vraiment le plus, c'est le fait que le titre de la barre de navigation se heurte au bouton gauche personnalisé, la taille des marges était moins importante et utilisée comme un outil pour faire de la place. La solution consiste à simplement ajouter un élément d'espacement aux éléments de gauche.

UIBarButtonItem* backButton = [[UIBarButtonItem alloc] initWithCustomView:button];
UIBarButtonItem* spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
controller.navigationItem.leftBarButtonItems = [NSArray arrayWithObjects:backButton, spacer, nil];
3
Sashah

Version Swift de la méthode layoutSubviews de la sous-classe UINavigationBar personnalisée de theedeveloper3214 ( https://stackoverflow.com/a/46660888/4189037 ):

override func layoutSubviews() {
    super.layoutSubviews();
    if #available(iOS 11, *){
        self.layoutMargins = UIEdgeInsets()
        for subview in self.subviews {
            if String(describing: subview.classForCoder).contains("ContentView") {
                let oldEdges = subview.layoutMargins
                subview.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: oldEdges.right)
            }
        }
    }
}
1

ne autre réponse peut être utile.

Ma solution: cela fonctionne dans iOS 9-12. Vous devez appeler fixNavigationItemsMargin (margin:) dans la fonction viewDidAppear ( _ animé: Bool) et viewDidLayoutSubviews () . fixNavigationItemsMargin (margin:) modifierait la pile UINavigationController.

vous pouvez appeler fixNavigationItemsMargin (margin:) dans BaseNavigationController, faites le travail commun. Et appelez fixNavigationItemsMargin (margin:) dans UIViewController pour une mise en page précise.

// do common initilizer
class BaseNavigationController: UINavigationController {
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    fixNavigationItemsMargin()
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    fixNavigationItemsMargin()
}
}

// do precise layout
class ViewController: UIViewController {
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    navigationController?.fixNavigationItemsMargin(40)
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    navigationController?.fixNavigationItemsMargin(40)
}
}

extension UINavigationController {
func fixNavigationItemsMargin(_ margin: CGFloat = 8) {
    let systemMajorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
    if systemMajorVersion >= 11 {
        // iOS >= 11
        guard let contentView = navigationBar.subviews
            .first(
                where: { sub in
                    String(describing: sub).contains("ContentView")
        }) else { return }

        // refer to: https://www.matrixprojects.net/p/uibarbuttonitem-ios11/
        // if rightBarButtonItems has not any custom views, then margin would be 8(320|375)/12(414)
        // should use customView
        let needAdjustRightItems: Bool
        if let currentVC = viewControllers.last,
            let rightItems = currentVC.navigationItem.rightBarButtonItems,
            rightItems.count > 0,
            rightItems.filter({ $0.customView != nil }).count > 0 {
            needAdjustRightItems = true
        } else {
            print("Use 8(320|375)/12(414), if need precious margin ,use UIBarButtonItem(customView:)!!!")
            needAdjustRightItems = false
        }

        let needAdjustLeftItems: Bool
        if let currentVC = viewControllers.last,
            let leftItems = currentVC.navigationItem.leftBarButtonItems,
            leftItems.count > 0,
            leftItems.filter({ $0.customView != nil }).count > 0 {
            needAdjustLeftItems = true
        } else {
            print("Use 8(320|375)/12(414), if need precious margin ,use UIBarButtonItem(customView:)!!!")
            needAdjustLeftItems = false
        }

        let layoutMargins: UIEdgeInsets
        if #available(iOS 11.0, *) {
            let directionInsets = contentView.directionalLayoutMargins
            layoutMargins = UIEdgeInsets(
                top: directionInsets.top,
                left: directionInsets.leading,
                bottom: directionInsets.bottom,
                right: directionInsets.trailing)
        } else {
            layoutMargins = contentView.layoutMargins
        }

        contentView.constraints.forEach(
            { cst in

                // iOS 11 the distance between rightest item and NavigationBar should be  margin
                // rightStackView trailing space is -margin / 2
                // rightestItem trailing to rightStackView trailing is -margin / 2
                let rightConstant = -margin / 2

                switch (cst.firstAttribute, cst.secondAttribute) {
                case (.leading, .leading), (.trailing, .trailing):

                    if let stackView = cst.firstItem as? UIStackView,
                        stackView.frame.minX < navigationBar.frame.midX {
                        // is leftItems
                        if needAdjustLeftItems {
                            cst.constant = margin - layoutMargins.left
                        }
                    } else if let layoutGuide = cst.firstItem as? UILayoutGuide,
                        layoutGuide.layoutFrame.minX < navigationBar.frame.midX {
                        // is leftItems
                        if needAdjustLeftItems {
                            cst.constant = margin - layoutMargins.left
                        }
                    }

                    if let stackView = cst.firstItem as? UIStackView,
                        stackView.frame.maxX > navigationBar.frame.midX {
                        // is rightItems
                        if needAdjustRightItems {
                            cst.constant = rightConstant
                        }
                    } else if let layoutGuide = cst.firstItem as? UILayoutGuide,
                        layoutGuide.layoutFrame.maxX > navigationBar.frame.midX {
                        // is rightItems
                        if needAdjustRightItems {
                            cst.constant = rightConstant
                        }
                    }

                default: break
                }

        })

        // ensure items space == 8, minispcae
        contentView.subviews.forEach(
            { subsub in
                guard subsub is UIStackView else { return }
                subsub.constraints.forEach(
                    { cst in
                        guard cst.firstAttribute == .width
                            || cst.secondAttribute == .width
                        else { return }
                        cst.constant = 0
                })
        })

    } else {
        // iOS < 11

        let versionItemsCount: Int
        if systemMajorVersion == 10 {
            // iOS 10 navigationItem.rightBarButtonItems == 0
            // space = 16(320|375) / 20(414)
            // should adjust margin
            versionItemsCount = 0
        } else {
            // iOS 9 navigationItem.rightBarButtonItems == 0
            // space = 8(320|375) / 12(414)
            // should not adjust margin
            versionItemsCount = 1
        }

        let spaceProducer = { () -> UIBarButtonItem in
            let spaceItem = UIBarButtonItem(
                barButtonSystemItem: .fixedSpace,
                target: nil,
                action: nil)
            spaceItem.width = margin - 16
            return spaceItem
        }
        if let currentVC = viewControllers.last,
            var rightItems = currentVC.navigationItem.rightBarButtonItems,
            rightItems.count > versionItemsCount,
            let first = rightItems.first {
            // ensure the first BarButtonItem is NOT fixedSpace
            if first.title == nil && first.image == nil && first.customView == nil {
                print("rightBarButtonItems SPACE SETTED!!!  SPACE: ", abs(first.width))

            } else {
                rightItems.insert(spaceProducer(), at: 0)

                // arranged right -> left
                currentVC.navigationItem.rightBarButtonItems = rightItems
            }
        }

        if let currentVC = viewControllers.last,
            var leftItems = currentVC.navigationItem.leftBarButtonItems,
            leftItems.count > versionItemsCount,
            let first = leftItems.first {
            if first.title == nil && first.image == nil && first.customView == nil {
                print("leftBarButtonItems  SPACE SETTED!!!  SPACE: ", abs(first.width))
            } else {
                leftItems.insert(spaceProducer(), at: 0)

                // arranged left -> right
                currentVC.navigationItem.leftBarButtonItems = leftItems
            }
        }
    }
}
}
1
Jules

J'ai eu le même problème. J'avais trois boutons en tant qu'éléments du bouton droit sur la vue de la pile de la barre de navigation. J'ai donc mis à jour les encarts des sous-vues de la barre de navigation.

override func viewWillLayoutSubviews() {
                super.viewWillLayoutSubviews()
                for view in (self.navigationController?.navigationBar.subviews)! {
                    view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11)
                }
            }

Voici 11 l'espace dont j'avais besoin. Cela peut être n'importe quoi selon vos besoins. De plus, si vous voulez les boutons avec 0 encarts, remplacez view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11) par view.layoutMargins = UIEdgeInsets.zero

1
vinny