2017-06-06 605 views
41

现在在iOS 11中,sizeThatFits方法不是从UINavigationBar子类中调用的。更改UINavigationBar的帧会导致毛刺和错误的插入。 那么,现在有什么想法如何自定义导航栏高度?iOS 11导航栏高度自定义

+0

iOS 11现在带有UINavigationBar的唯一新API是: 'open var prefersLargeTitles:Bool',默认值为'false'。 – Dean

+0

检查发行说明中的​​已知问题,因为它处于测试阶段。 –

+0

我有同样的问题,我的自定义大小导航栏非常糟糕,我的旧代码无法正常工作。 –

回答

4

尽管它已在beta 4中修复,但似乎导航栏的背景图像并未与实际视图(您可以通过在视图层次结构查看器中查看来验证此功能)进行比例缩放。一种用于现在的解决方法是重写layoutSubviews在自定义UINavigationBar,然后使用此代码:

- (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]; 
    } 
    } 
} 

如果你发现,其实栏背景具有的-20偏移,使其显示在状态栏的后面,所以上述计算还指出在

+0

你需要声明/ instanciate subviewFrame ?或直接编辑子视图的框架? –

+1

@MarcoPappalardo固定错字,需要是一个局部变量 – strangetimes

9

补充: 问题是在IOS 11的β6解决,所以下面的代码是没有用^ _ ^的


原来的答复:

解决与以下代码:

(I总是希望navigationBar.height + statusBar.height == 64是否隐藏的状态条是真还是假)

@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

在for看你的'subview'是一个UIView。你以后怎么做'subview.height'? –

+0

我写了一个UIView的助手类。 – CharlieSu

+0

iOS 11 beta 9仍然存在此问题。使用此解决方法可解决问题。但希望他们能解决它。谢谢@CharlieSu –

2

随着首要-layoutSubviews-setFrame:如果您不希望调整大小的导航栏隐藏您的内容,则应该查看新添加的UIViewController的additionalSafereaInsets属性(Apple Documentation)。

+0

这很重要,只需更新导航栏背景高度就可以使其与视图控制器中的内容重叠。我无法解决的是如何正确使用'addionalSafeAreaInsets',特别是如何允许iOS 10及以下版本不支持此属性 – JoGoFo

+0

这很重要,只需更新导航栏背景高度即可在视图控制器中重叠内容。我无法解决的是如何正确使用'addionalSafeAreaInsets',特别是如何允许iOS 10及以下版本不支持该属性 – JoGoFo

+0

如果你找到了好的解决方案你能分享吗? –

3

在Xcode 9 Beta 6我仍然有问题。酒吧总是看起来像素高度为44,并在状态栏下按下。

为了解决我做了一个子类与@strangetimes代码(SWIFT)

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 
     } 
    } 
    } 
} 

和我比放在状态栏

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

这个工作对我来说下吧:

- (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]; 
     } 
    } 
} 
2

简化了斯威夫特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 
       } 
      } 
     } 
    } 
} 
1

这就是我使用的。如果使用UISearchBar作为标题或其他视图来修改小节内容的大小,则它适用于常规内容(44.0 px),因此必须相应地更新这些值。由于它可能会在某些时候刹车,因此使用这个需要您自担风险。

这是具有90.0px高度硬编码的导航栏,适用于iOS 11和更早版本。您可能需要为iOS 11之前版本的UIBarButtonItem添加一些插页以使其看起来相同。

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) 

      } 
     } 
    } 
} 

而且你将它添加到UINavigationController子是这样的:

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") 
    } 

} 
+0

我得到了一个错误 - >致命错误:初始化(编码器:)尚未实现: –

+0

只需实现与编码器的初始化,如果您使用的是 – Jelly

+0

感谢您的答复。但安全区的顶部不会更新。安全区域的顶部仍然是44px。如何在设置导航栏高度后更新安全区域的顶部。 –

9

更新2018年1月7日

此代码是支持的XCode 9.2,iOS的11.2

我有同样的问题。以下是我的解决方案。我假设身高尺寸为66.

请选择我的答案,如果它可以帮助你。

创建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() 

      } 
     } 


    } 


} 

集故事板

enter image description here

Set NavigationBar class

设置自定义导航栏类​​

Add TestView

enter image description here

添加TestView +集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

黄色是barbackgroundView。黑色不透明是BarContentView。

而我删除了BarContentView的backgroundColor。

enter image description here

就是这样。

+0

谢谢! 'setAdditionalSafeAreaInsets:'帮助了我! :)因此,iOS 11中的导航栏在高度上与iOS 10中的相比较短? – KarenAnne

+1

此解决方案在iOS 11.2中似乎无效,因为导航栏多次调用layoutSubviews(),导致应用程序冻结。 – Michael

+1

我也面临同样的问题@Michael –

0

我将导航栏的高度翻倍,因此我可以在默认导航控件上添加一行状态图标,方法是继承UINavigationBar并使用sizeThatFits来覆盖高度。幸运的是,这具有相同的效果,并且更简单,副作用更少。我到11与iOS 8测试它在您的视图控制器将这个:

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

大多数问题的答案,也许以上个人解决问题或工作对很多人,但我发现有用的,它解决了我的问题用的下面将解释几行代码。

在我的情况我对导航栏和状态栏隐藏的自定义类。而且自从iOS 11发布以来,当模态控制器被解雇时,我的导航栏开始向上移动。

然后我发现sizeThatFits只会触发一次,当bar在iOS 11上初始化时,会阻止更改为自定义大小。

所有我需要做的是在偏移了吧对Y我的自定义类添加一个变种,并将其设置基于系统的版本,所以:

定义宏的系统版本,如果在目标C以其它方式使用如果#available

if SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"11.0") { 
    //custom y offset 
    customYoffset = 20.0; 
} else { 
    customYoffset = 0; 
} 

//set the custom y Offset in the setFrame function 
-(void)setFrame:(CGRect)frame { 
    if ([UIApplication sharedApplication].isStatusBarHidden) { 
     frame.size.height = customHeight; 
     frame.origin.y = customYoffset; 
    } 
    [super setFrame:frame]; 
} 

这就是那一切完美, 这是ASUMING您的NAV条清晰彩色

6

根据苹果开发者(看here,herehere),则不支持更改iOS 11中的导航栏高度。 Here他们建议做一些解决方法,比如在导航栏下(但在其外部)有一个视图,然后删除导航栏边框。其结果是,你将在故事板有这样的:

enter image description here

这个样子的设备上:

enter image description here

现在你可以做到这一点在其他答案提出了一个解决办法:创建一个自定义的子类UINavigationBar,添加你的自定义大型子视图到它,覆盖sizeThatFitslayoutSubviews,然后设置additionalSafeAreaInsets.top为导航的顶部控制器的差异customHeight - 44px,但条vi ew仍然是默认的44px,尽管视觉上一切看起来都很完美。但是,我没有尝试覆盖setFrame,可能它有效,但是,正如Apple开发人员在上面的链接之一中所写:“...并且都不支持更改由UINavigationController拥有的导航栏的框架(导航控制器会在它认为合适的时候高兴地跺脚你的车架变化)。“

在我的情况下,上述的解决方法提出意见,看起来像这样(调试视图来显示边框):

enter image description here

正如你所看到的,外观是相当不错,additionalSafeAreaInsets正确地推内容向下,大导航栏是可见的,但是我在这个栏中有一个自定义按钮,只有在标准44像素导航栏下的区域才可点击(图像中的绿色区域)。触摸下方的标准导航栏高度不会达到我的自定义子视图,所以我需要调整导航栏本身的大小,Apple开发人员说这不支持。

+2

我喜欢这个回答最好。至少,这不会在未来破裂。希望苹果能够提供更好的方法来解决这个问题。但是肯定在晚上救了我的屁股。谢谢。 – deeJ

+0

要解决可点击区域的问题,请尝试添加到您的自定义UINavigationBar下一个覆盖方法 '代码 覆盖func hitTest(_点:CGPoint,事件:UIEvent?) - > UIView? { 返回subviews.reduce(super.hitTest(point,with:event)){(result,subview) 返回结果??子视图。hitTest(convert(point,to:subview),with:event) } } ' 抱歉格式化 – MarkII