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:
auf dem Gerät so aussehen:
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):
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.
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
Legen Sie die Custom NavigationBar-Klasse fest
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.
}
}
Gelb ist barbackgroundView. Schwarze Deckkraft ist BarContentView.
Und ich habe die Hintergrundfarbe von BarContentView entfernt.
Das ist es.
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
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];
}
}
}
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
}
}
}
}
}
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.
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.
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)
)
)
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")
}
}
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
}
}
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.
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.
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
}
}