77 votes

Personnalisation de la hauteur de la barre de navigation d'iOS 11

Désormais, dans iOS 11, le sizeThatFits n'est pas appelée par la méthode UINavigationBar sous-classes. Changer le cadre de UINavigationBar provoque des problèmes et des insertions erronées. Alors, une idée pour personnaliser la hauteur de la barre de navigation ?

0 votes

La seule nouvelle API avec iOS 11 pour le moment avec UINavigationBar est : open var prefersLargeTitles: Bool et la valeur par défaut est false .

0 votes

Vérifiez les problèmes connus dans les notes de publication, car il s'agit d'une version bêta.

0 votes

J'ai le même problème, ma barre de navigation de taille personnalisée est très glitchy et mon ancien code ne fonctionne pas correctement.

43voto

frangulyan Points 1203

Selon les développeurs d'Apple (regardez aquí , aquí y aquí ), la modification de la hauteur de la barre de navigation dans iOS 11 n'est pas prise en charge. Ici ils suggèrent de faire des solutions de contournement comme avoir une vue sous la barre de navigation (mais à l'extérieur de celle-ci) et ensuite supprimer la bordure de la barre de navigation. En conséquence, vous aurez ceci dans le storyboard :

enter image description here

ressemble à ça sur l'appareil :

enter image description here

Maintenant vous pouvez faire une solution de contournement qui a été suggérée dans les autres réponses : créer une sous-classe personnalisée de UINavigationBar ajoutez-y votre grande vue secondaire personnalisée, remplacez-la par sizeThatFits y layoutSubviews alors, il y a additionalSafeAreaInsets.top pour le contrôleur supérieur de la navigation à la différence customHeight - 44px mais la vue de la barre sera toujours de 44px par défaut, même si visuellement tout sera parfait. Je n'ai pas essayé de remplacer setFrame Cependant, comme l'a écrit un développeur Apple dans l'un des liens ci-dessus, cela peut fonctionner : "... et il n'est pas non plus possible de modifier le cadre d'une barre de navigation qui appartient à un UINavigationController (le contrôleur de navigation piétinera volontiers vos modifications de cadre dès qu'il le jugera bon)."

Dans mon cas, la solution ci-dessus a permis aux vues de ressembler à ceci (vue de débogage pour montrer les bordures) :

enter image description here

Comme vous pouvez le constater, l'aspect visuel est assez bon, la additionalSafeAreaInsets correctement poussé le contenu vers le bas, la grande barre de navigation est visible, cependant j'ai un bouton personnalisé dans cette barre et seule la zone qui va sous la barre de navigation standard de 44 pixels est cliquable (zone verte dans l'image). Les touches situées en dessous de la hauteur de la barre de navigation standard n'atteignent pas ma sous-vue personnalisée. J'ai donc besoin que la barre de navigation elle-même soit redimensionnée, ce qui n'est pas pris en charge selon les développeurs d'Apple.

2 votes

Pour résoudre le problème de la zone cliquable, essayez d'ajouter à votre barre de navigation UINavigationBar personnalisée la méthode de remplacement suivante code override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return subviews.reduce(super.hitTest(point, with: event)) { (result, subview) in return result ?? subview.hitTest(convert(point, to: subview), with: event) } } Désolé pour le formatage

7 votes

Le dernier projet fourni par Apple n'inclut pas la barre de navigation étendue.

1 votes

@Weizhi vous pouvez télécharger l'ancienne version depuis github : github.com/robovm/apple-ios-samples/tree/master/

25voto

Shawn Baek Points 727

Mis à jour le 07 janvier 2018

Ce code est compatible avec XCode 9.2 et iOS 11.2.

J'ai eu le même problème. Voici ma solution. Je suppose que la taille de la hauteur est de 66.

Veuillez choisir ma réponse si elle vous aide.

Créer CINavgationBar.swift

   import UIKit

@IBDesignable
class CINavigationBar: UINavigationBar {

    //set NavigationBar's height
    @IBInspectable var customHeight : CGFloat = 66

    override func sizeThatFits(_ size: CGSize) -> CGSize {

        return CGSize(width: UIScreen.main.bounds.width, height: customHeight)

    }

    override func layoutSubviews() {
        super.layoutSubviews()

        print("It called")

        self.tintColor = .black
        self.backgroundColor = .red

        for subview in self.subviews {
            var stringFromClass = NSStringFromClass(subview.classForCoder)
            if stringFromClass.contains("UIBarBackground") {

                subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: customHeight)

                subview.backgroundColor = .green
                subview.sizeToFit()
            }

            stringFromClass = NSStringFromClass(subview.classForCoder)

            //Can't set height of the UINavigationBarContentView
            if stringFromClass.contains("UINavigationBarContentView") {

                //Set Center Y
                let centerY = (customHeight - subview.frame.height) / 2.0
                subview.frame = CGRect(x: 0, y: centerY, width: self.frame.width, height: subview.frame.height)
                subview.backgroundColor = .yellow
                subview.sizeToFit()

            }
        }

    }

}

Set Storyboard

enter image description here

Set NavigationBar class

Définir la classe de la barre de navigation personnalisée

Add TestView

enter image description here

Ajouter TestView + Définir SafeArea

ViewController.swift

import UIKit

class ViewController: UIViewController {

    var navbar : UINavigationBar!

    @IBOutlet weak var testView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        //update NavigationBar's frame
        self.navigationController?.navigationBar.sizeToFit()
        print("NavigationBar Frame : \(String(describing: self.navigationController!.navigationBar.frame))")

    }

    //Hide Statusbar
    override var prefersStatusBarHidden: Bool {

        return true
    }

    override func viewDidAppear(_ animated: Bool) {

        super.viewDidAppear(false)

        //Important!
        if #available(iOS 11.0, *) {

            //Default NavigationBar Height is 44. Custom NavigationBar Height is 66. So We should set additionalSafeAreaInsets to 66-44 = 22
            self.additionalSafeAreaInsets.top = 22

        }

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

SecondViewController.swift

import UIKit

class SecondViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.

        // Create BackButton
        var backButton: UIBarButtonItem!
        let backImage = imageFromText("Back", font: UIFont.systemFont(ofSize: 16), maxWidth: 1000, color:UIColor.white)
        backButton = UIBarButtonItem(image: backImage, style: UIBarButtonItemStyle.plain, target: self, action: #selector(SecondViewController.back(_:)))

        self.navigationItem.leftBarButtonItem = backButton
        self.navigationItem.leftBarButtonItem?.setBackgroundVerticalPositionAdjustment(-10, for: UIBarMetrics.default)

    }
    override var prefersStatusBarHidden: Bool {

        return true
    }
    @objc func back(_ sender: UITabBarItem){

        self.navigationController?.popViewController(animated: true)

    }

    //Helper Function : Get String CGSize
    func sizeOfAttributeString(_ str: NSAttributedString, maxWidth: CGFloat) -> CGSize {
        let size = str.boundingRect(with: CGSize(width: maxWidth, height: 1000), options:(NSStringDrawingOptions.usesLineFragmentOrigin), context:nil).size
        return size
    }

    //Helper Function : Convert String to UIImage
    func imageFromText(_ text:NSString, font:UIFont, maxWidth:CGFloat, color:UIColor) -> UIImage
    {
        let paragraph = NSMutableParagraphStyle()
        paragraph.lineBreakMode = NSLineBreakMode.byWordWrapping
        paragraph.alignment = .center // potentially this can be an input param too, but i guess in most use cases we want center align

        let attributedString = NSAttributedString(string: text as String, attributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.paragraphStyle:paragraph])

        let size = sizeOfAttributeString(attributedString, maxWidth: maxWidth)
        UIGraphicsBeginImageContextWithOptions(size, false , 0.0)
        attributedString.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image!
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

enter image description here enter image description here

Le jaune est le barbackgroundView. L'opacité noire est BarContentView.

Et j'ai supprimé le backgroundColor de BarContentView.

enter image description here

C'est ça.

0 votes

Merci ! Le site setAdditionalSafeAreaInsets: m'a aidé :) La barre de navigation d'iOS 11 est donc plus courte en termes de hauteur que celle d'iOS 10 ?

1 votes

Cette solution semble être invalide dans iOS 11.2 parce que la barre de navigation appelle layoutSubviews() plusieurs fois, ce qui fait que l'application se fige.

1 votes

Je suis également confronté au même problème @Michael

10voto

Minster.Zo Points 81

Cela fonctionne pour moi :

- (CGSize)sizeThatFits:(CGSize)size {
    CGSize sizeThatFit = [super sizeThatFits:size];
    if ([UIApplication sharedApplication].isStatusBarHidden) {
        if (sizeThatFit.height < 64.f) {
            sizeThatFit.height = 64.f;
        }
    }
    return sizeThatFit;
}

- (void)setFrame:(CGRect)frame {
    if ([UIApplication sharedApplication].isStatusBarHidden) {
        frame.size.height = 64;
    }
    [super setFrame:frame];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    for (UIView *subview in self.subviews) {
        if ([NSStringFromClass([subview class]) containsString:@"BarBackground"]) {
            CGRect subViewFrame = subview.frame;
            subViewFrame.origin.y = 0;
            subViewFrame.size.height = 64;
            [subview setFrame: subViewFrame];
        }
        if ([NSStringFromClass([subview class]) containsString:@"BarContentView"]) {
            CGRect subViewFrame = subview.frame;
            subViewFrame.origin.y = 20;
            subViewFrame.size.height = 44;
            [subview setFrame: subViewFrame];
        }
    }
}

9voto

CharlieSu Points 91

Ajouté : Le problème est résolu dans iOS 11 beta 6 ,donc le code ci-dessous est inutile ^_^


Réponse originale :

Résolu avec le code ci-dessous :

(Je veux toujours que la hauteur de la barre de navigation + la hauteur de la barre de statut == 64, que la barre de statut soit cachée ou non).

 @implementation P1AlwaysBigNavigationBar

- (CGSize)sizeThatFits:(CGSize)size {
    CGSize sizeThatFit = [super sizeThatFits:size];
    if ([UIApplication sharedApplication].isStatusBarHidden) {
        if (sizeThatFit.height < 64.f) {
            sizeThatFit.height = 64.f;
        }
    }
    return sizeThatFit;
}

- (void)setFrame:(CGRect)frame {
    if ([UIApplication sharedApplication].isStatusBarHidden) {
        frame.size.height = 64;
    }
    [super setFrame:frame];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (![UIApplication sharedApplication].isStatusBarHidden) {
        return;
    }

    for (UIView *subview in self.subviews) {
        NSString* subViewClassName = NSStringFromClass([subview class]);
        if ([subViewClassName containsString:@"UIBarBackground"]) {
            subview.frame = self.bounds;
        }else if ([subViewClassName containsString:@"UINavigationBarContentView"]) {
            if (subview.height < 64) {
                subview.y = 64 - subview.height;
            }else {
                subview.y = 0;
            }
        }
    }
}
@end

1 votes

Dans le pour regarder votre subview est un UIView. Comment faites-vous subview.height plus tard ?

0 votes

J'ai écrit une catégorie d'aide pour UIView.

0 votes

J'ai toujours ce problème avec iOS 11 beta 9. L'utilisation de cette solution de contournement résout le problème. Mais j'espère qu'ils vont le corriger. Merci @CharlieSu

6voto

Peymankh Points 634

Simplifié avec Swift 4.

class CustomNavigationBar : UINavigationBar {

    private let hiddenStatusBar: Bool

    // MARK: Init
    init(hiddenStatusBar: Bool = false) {
        self.hiddenStatusBar = hiddenStatusBar
        super.init(frame: .zero)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: Overrides
    override func layoutSubviews() {
        super.layoutSubviews()

        if #available(iOS 11.0, *) {
            for subview in self.subviews {
                let stringFromClass = NSStringFromClass(subview.classForCoder)
                if stringFromClass.contains("BarBackground") {
                    subview.frame = self.bounds
                } else if stringFromClass.contains("BarContentView") {
                    let statusBarHeight = self.hiddenStatusBar ? 0 : UIApplication.shared.statusBarFrame.height
                    subview.frame.origin.y = statusBarHeight
                    subview.frame.size.height = self.bounds.height - statusBarHeight
                }
            }
        }
    }
}

0 votes

Ce code me donne une erreur fatale fatalError("init(coder :) n'a pas été implémenté")

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X