wake-up-neo.com

anpassung der Höhe der iOS 11-Navigationsleiste

In iOS 11 wird die sizeThatFits-Methode nicht von UINavigationBar-Unterklassen aufgerufen. Das Ändern des Rahmens von UINavigationBar verursacht Störungen und falsche Einfügungen.

Laut Apple-Entwicklern (siehe hier , hier und hier ) wird das Ändern der Höhe der Navigationsleiste in iOS 11 nicht unterstützt. Hier Sie schlagen vor, Abhilfemaßnahmen auszuführen, z. B. eine Ansicht unter der Navigationsleiste (aber außerhalb der Navigationsleiste), und dann die Navigationsleiste zu entfernen. Als Ergebnis erhalten Sie dies im Storyboard:

 enter image description here

auf dem Gerät so aussehen:

 enter image description here

Jetzt können Sie eine Problemumgehung ausführen, die in den anderen Antworten vorgeschlagen wurde: Erstellen Sie eine benutzerdefinierte Unterklasse von UINavigationBar, fügen Sie Ihre benutzerdefinierte große Unteransicht hinzu, überschreiben Sie sizeThatFits und layoutSubviews, und setzen Sie additionalSafeAreaInsets.top für den obersten Controller der Navigation auf customHeight - 44px, aber die Leiste Die Ansicht ist immer noch die Standardeinstellung von 44px, auch wenn optisch alles perfekt aussieht. Ich habe nicht versucht, setFrame zu überschreiben, vielleicht funktioniert es jedoch, wie Apple-Entwickler in einem der obigen Links geschrieben hat: "... und keiner wird [unterstützt] den Rahmen einer Navigationsleiste ändern, die einem UINavigationController gehört (Der Navigationscontroller stampft gerne auf die Änderungen Ihres Rahmens, wann immer er es für angebracht hält). "

In meinem Fall sah die obige Problemumgehung folgendermaßen aus (Debug-Ansicht, um Grenzen anzuzeigen):

 enter image description here

Wie Sie sehen, ist das visuelle Erscheinungsbild ziemlich gut, die additionalSafeAreaInsets hat den Inhalt korrekt nach unten gedrückt, die große Navigationsleiste ist sichtbar, jedoch habe ich eine benutzerdefinierte Schaltfläche in dieser Leiste und nur der Bereich, der unter der standardmäßigen 44-Pixel-Navigationsleiste liegt anklickbar (grüner Bereich im Bild). Berührungen unterhalb der Standardhöhe der Navigationsleiste erreichen meine benutzerdefinierte Unteransicht nicht. Daher muss die Größe der Navigationsleiste selbst geändert werden, was laut Apple-Entwicklern nicht unterstützt wird.

25
frangulyan

Aktualisiert am 07 Jan 2018

Dieser Code unterstützt XCode 9.2, iOS 11.2

Ich hatte das gleiche Problem. Unten ist meine Lösung. Ich gehe davon aus, dass die Größe 66 beträgt. 

Bitte wählen Sie meine Antwort, wenn es Ihnen hilft.

Erstellen Sie 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()

            }
        }


    }


}

Storyboard einstellen

 enter image description here

 Set NavigationBar class

Legen Sie die Custom NavigationBar-Klasse fest

 Add TestView

 enter image description here

Fügen Sie TestView + Set SafeArea hinzu

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

Gelb ist barbackgroundView. Schwarze Deckkraft ist BarContentView.

Und ich habe die Hintergrundfarbe von BarContentView entfernt.

 enter image description here

Das ist es.

20
Shawn Baek

Hinzugefügt: Das Problem wurde in iOS 11 Beta 6 gelöst, so dass der folgende Code nichts nützt ^ _ ^


Ursprüngliche Antwort:

Mit dem folgenden Code gelöst:

(Ich möchte immer die navigationBar.height + statusBar.height == 64, ob das ausgeblendete StatusBar wahr ist oder nicht)

 @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
9
CharlieSu

das funktioniert für mich:

- (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];
        }
    }
}
8
Minster.Zo

Vereinfacht mit 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
                }
            }
        }
    }
}
4
Peymankh

Neben dem Überschreiben von -layoutSubviews und -setFrame: sollten Sie die neu hinzugefügte UIViewController-Eigenschaft additionalSafereaInsets ( Apple Documentation ) überprüfen, wenn die verkleinerte Navigationsleiste Ihren Inhalt nicht verbergen soll.

4
Reggian

Obwohl es in Beta 4 behoben ist, scheint das Hintergrundbild der Navigationsleiste nicht mit der tatsächlichen Ansicht zu skalieren (Sie können dies durch Betrachten in der Ansichtshierarchie-Ansicht überprüfen). Um dieses Problem zu umgehen, können Sie layoutSubviews in Ihrer benutzerdefinierten UINavigationBar überschreiben und dann diesen Code verwenden:

- (void)layoutSubviews
{
  [super layoutSubviews];

  for (UIView *subview in self.subviews) {
    if ([NSStringFromClass([subview class]) containsString:@"BarBackground"]) {
        CGRect subViewFrame = subview.frame;
        subViewFrame.Origin.y = -20;
        subViewFrame.size.height = CUSTOM_FIXED_HEIGHT+20;
        [subview setFrame: subViewFrame];
    }
  }
}

Wenn Sie feststellen, hat der Balkenhintergrund tatsächlich einen Versatz von -20, damit er hinter der Statusleiste angezeigt wird. Die obige Berechnung fügt das hinzu.

4
strangetimes

auf Xcode 9 Beta 6 habe ich immer noch das Problem. Die Leiste sieht immer 44 Pixel hoch aus und wird unter die Statusleiste geschoben.

Um das zu lösen, habe ich eine Unterklasse mit @strangetimes-Code (in Swift) erstellt.

class NavigationBar: UINavigationBar {

  override func layoutSubviews() {
    super.layoutSubviews()

    for subview in self.subviews {
      var stringFromClass = NSStringFromClass(subview.classForCoder)
      print("--------- \(stringFromClass)")
      if stringFromClass.contains("BarBackground") {
        subview.frame.Origin.y = -20
        subview.frame.size.height = 64
      }
    }
  }
}

und ich platziere die Leiste tiefer als die Statusleiste

let newNavigationBar = NavigationBar(frame: CGRect(Origin: CGPoint(x: 0,
                                                                       y: 20),
                                                         size: CGSize(width: view.frame.width,
                                                                      height: 64)
      )
    ) 
3

Das ist was ich benutze. Dies funktioniert bei normalem Inhalt (44.0 px), wenn Sie UISearchBar als Titel oder andere Ansichten verwenden, die die Größe des Balkeninhalts ändern, müssen Sie die Werte entsprechend aktualisieren. Verwenden Sie dies auf eigene Gefahr, da es irgendwann bremsen kann.

Dies ist die Navbar mit 90.0px Höhe, die sowohl für iOS 11 als auch für ältere Versionen geeignet ist. Möglicherweise müssen Sie der UIBarButtonItem einige Einfügungen hinzufügen, damit vor iOS 11 dasselbe Aussehen erzielt wird.

class NavBar: UINavigationBar {

    override init(frame: CGRect) {
        super.init(frame: frame)

        if #available(iOS 11, *) {
            translatesAutoresizingMaskIntoConstraints = false
        }
    }

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

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return CGSize(width: UIScreen.main.bounds.width, height: 70.0)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        guard #available(iOS 11, *) else {
            return
        }

        frame = CGRect(x: frame.Origin.x, y:  0, width: frame.size.width, height: 90)

        if let parent = superview {
            parent.layoutIfNeeded()

            for view in parent.subviews {
                let stringFromClass = NSStringFromClass(view.classForCoder)
                if stringFromClass.contains("NavigationTransition") {
                    view.frame = CGRect(x: view.frame.Origin.x, y: frame.size.height - 64, width: view.frame.size.width, height: parent.bounds.size.height - frame.size.height + 4)
                }
            }
        }

        for subview in self.subviews {
            var stringFromClass = NSStringFromClass(subview.classForCoder)
            if stringFromClass.contains("BarBackground") {
                subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: 90)
                subview.backgroundColor = .yellow
            }

            stringFromClass = NSStringFromClass(subview.classForCoder)
            if stringFromClass.contains("BarContent") {
                subview.frame = CGRect(x: subview.frame.Origin.x, y: 40, width: subview.frame.width, height: subview.frame.height)

            }
        }
    }
}

Und Sie fügen es einer UINavigationController-Unterklasse wie folgt hinzu:

class CustomBarNavigationViewController: UINavigationController {

    init() {
        super.init(navigationBarClass: NavBar.self, toolbarClass: nil)
    }

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    override init(rootViewController: UIViewController) {
        super.init(navigationBarClass: NavBar.self, toolbarClass: nil)

        self.viewControllers = [rootViewController]
    }

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

}
2
Jelly

Ich habe die Höhe meiner Navigationsleiste verdoppelt, sodass ich über den Standardnavigationssteuerelementen eine Reihe von Statussymbolen hinzufügen konnte, indem ich UINavigationBar subclassing und sizeThatFits zum Überschreiben der Höhe verwendet. Glücklicherweise hat dies den gleichen Effekt und ist einfacher mit weniger Nebenwirkungen. Ich habe es mit iOS 8 bis 11 getestet. Fügen Sie dies in Ihren View-Controller ein:

- (void)viewDidLoad {
    [super viewDidLoad];
    if (self.navigationController) {
        self.navigationItem.Prompt = @" "; // this adds empty space on top
    }
}
0
arlomedia

Dies funktioniert gut für die normale Navigationsleiste. Wenn Sie den LargeTitle verwenden, funktioniert dies nicht gut, da die titleView-Größe keine feste Höhe von 44 Punkten sein wird. Für die normale Sicht sollte dies jedoch ausreichen.

Wie @frangulyan schlug Apple vor, eine Ansicht unter der Navigationsleiste hinzuzufügen und die dünne Linie (Schattenbild) auszublenden. Das habe ich mir unten ausgedacht. Ich habe der titleView des navigationItems eine uiview hinzugefügt und dann eine imageView innerhalb dieser uiview hinzugefügt. Ich habe die dünne Linie (Schattenbild) entfernt. Die von mir hinzugefügte uiview ist die exakt gleiche Farbe wie die navBar . Ich habe ein uiLabel in diese Ansicht eingefügt und das war's.

 enter image description here 

Hier ist das 3D-Bild. Die erweiterte Ansicht befindet sich hinter dem BenutzernamenLabel unter der Navigationsleiste. Es ist grau und hat eine dünne Linie darunter. Verankern Sie einfach Ihre collectionView oder was auch immer unter der dünnen separatorLine.

 enter image description here 

Die 9 Schritte werden über jeder Codezeile erklärt:

class ExtendedNavController: UIViewController {

    fileprivate let extendedView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .white
        return view
    }()

    fileprivate let separatorLine: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .gray
        return view
    }()

    fileprivate let usernameLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.systemFont(ofSize: 14)
        label.text = "username goes here"
        label.textAlignment = .center
        label.lineBreakMode = .byTruncatingTail
        label.numberOfLines = 1
        return label
    }()

    fileprivate let myTitleView: UIView = {
        let view = UIView()
        view.backgroundColor = .white
        return view
    }()

    fileprivate let profileImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.clipsToBounds = true
        imageView.backgroundColor = .darkGray
        return imageView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        // 1. the navBar's titleView has a height of 44, set myTitleView height and width both to 44
        myTitleView.frame = CGRect(x: 0, y: 0, width: 44, height: 44)

        // 2. set myTitleView to the nav bar's titleView
        navigationItem.titleView = myTitleView

        // 3. get rid of the thin line (shadow Image) underneath the navigationBar
        navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
        navigationController?.navigationBar.layoutIfNeeded()

        // 4. set the navigationBar's tint color to the color you want
        navigationController?.navigationBar.barTintColor = UIColor(red: 249.0/255.0, green: 249.0/255.0, blue: 249.0/255.0, alpha: 1.0)

        // 5. set extendedView's background color to the same exact color as the navBar's background color
        extendedView.backgroundColor = UIColor(red: 249.0/255.0, green: 249.0/255.0, blue: 249.0/255.0, alpha: 1.0)

        // 6. set your imageView to get pinned inside the titleView
        setProfileImageViewAnchorsInsideMyTitleView()

        // 7. set the extendedView's anchors directly underneath the navigation bar
        setExtendedViewAndSeparatorLineAnchors()

        // 8. set the usernameLabel's anchors inside the extendedView
        setNameLabelAnchorsInsideTheExtendedView()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(true)

        // 9. **Optional** If you want the shadow image to show on other view controllers when popping or pushing
        navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
        navigationController?.navigationBar.setValue(false, forKey: "hidesShadow")
        navigationController?.navigationBar.layoutIfNeeded()
    }

    func setExtendedViewAndSeparatorLineAnchors() {

        view.addSubview(extendedView)
        view.addSubview(separatorLine)

        extendedView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        extendedView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        extendedView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        extendedView.heightAnchor.constraint(equalToConstant: 29.5).isActive = true

        separatorLine.topAnchor.constraint(equalTo:  extendedView.bottomAnchor).isActive = true
        separatorLine.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        separatorLine.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        separatorLine.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
    }

    func setProfileImageViewAnchorsInsideMyTitleView() {

        myTitleView.addSubview(profileImageView)

        profileImageView.topAnchor.constraint(equalTo: myTitleView.topAnchor).isActive = true
        profileImageView.centerXAnchor.constraint(equalTo: myTitleView.centerXAnchor).isActive = true
        profileImageView.widthAnchor.constraint(equalToConstant: 44).isActive = true
        profileImageView.heightAnchor.constraint(equalToConstant: 44).isActive = true

        // round the profileImageView
        profileImageView.layoutIfNeeded()
        profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
    }

    func setNameLabelAnchorsInsideTheExtendedView() {

        extendedView.addSubview(usernameLabel)

        usernameLabel.topAnchor.constraint(equalTo: extendedView.topAnchor).isActive = true
        usernameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        usernameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    }
}
0
Lance Samaria