iOS 11导航栏高度自定义
现在在iOS 11中, sizeThatFits
方法不是从UINavigationBar
子类中调用的。 更改UINavigationBar
的框架会导致毛刺和错误的插入。 那么,现在有什么想法如何自定义导航栏高度?
补充:在iOS 11 beta 6中解决了这个问题,所以下面的代码是没有用的^ _ ^
原始答案:
用下面的代码解决:
(我总是希望navigationBar.height + statusBar.height == 64 statusBar的隐藏是否为true)
@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
虽然它已经在beta 4中修复了,但是看起来导航栏的背景图片并没有与实际的视图进行比例缩放(您可以通过在视图层次结构查看器中查看来进行validation)。 目前的解决方法是重写您的自定义UINavigationBar
layoutSubviews
,然后使用以下代码:
- (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
,使其出现在状态栏的后面,所以上面的计算将其添加到中。
这对我有用:
- (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]; } } }
除了覆盖-layoutSubviews
和-setFrame:
如果你不想resize的导航栏隐藏你的内容,你应该检查出新添加的UIViewController的additionalSafereaInsets
属性( Apple文档 )。
在Xcode 9 Beta 6我仍然有这个问题。 酒吧总是看起来44像素的高度,并推到状态栏下。
为了解决我使用@strangetimes代码(在Swift中)创build了一个子类,
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) ) )
用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 } } } } }
这是我用的。 它适用于普通内容(44.0像素),如果您使用UISearchBar
作为标题或其他视图来修改酒吧内容的大小,您必须相应地更新值。 由于可能在某个时刻刹车,因此请自行承担风险。
这是硬编码高度为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") } }
我有同样的问题。 以下是我的解决scheme。 我假设身高尺寸是66。
我的解决scheme工作正常iOS 10,11。
请select我的答案,如果它可以帮助你。
创buildNavgationBar.swift
import UIKit class NavigationBar: UINavigationBar { //set NavigationBar's height var customHeight : CGFloat = 66 override func sizeThatFits(_ size: CGSize) -> CGSize { return CGSize(width: UIScreen.main.bounds.width, height: customHeight) } override func layoutSubviews() { super.layoutSubviews() frame = CGRect(x: frame.origin.x, y: 0, width: frame.size.width, height: customHeight) // title position (statusbar height / 2) setTitleVerticalPositionAdjustment(-10, for: UIBarMetrics.default) 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: customHeight) subview.backgroundColor = .yellow } stringFromClass = NSStringFromClass(subview.classForCoder) if stringFromClass.contains("BarContent") { subview.frame = CGRect(x: subview.frame.origin.x, y: 20, width: subview.frame.width, height: customHeight - 20) subview.backgroundColor = UIColor(red: 20/255, green: 20/255, blue: 20/255, alpha: 0.4) } } } }
设置故事板
设置自定义NavigationBar类
添加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. } }
黄色是barbackgroundView。 黑色不透明是BarContentView。
我删除了BarContentView的backgroundColor。
而已。
我将导航栏的高度加倍,这样我就可以在默认导航控件的上方添加一行状态图标,通过inheritanceUINavigationBar并使用sizeThatFits来覆盖高度。 幸运的是,这具有相同的效果,并且更简单,副作用更less。 我用iOS 8到11testing了一下。把它放在你的视图控制器中:
- (void)viewDidLoad { [super viewDidLoad]; if (self.navigationController) { self.navigationItem.prompt = @" "; // this adds empty space on top } }
上面的大部分答案都可以解决个人问题或者为很多人工作,但是我发现有用的东西,并且用几行代码解决了我的问题。
在我的情况下, 我有 隐藏 的导航栏 和状态栏 的自定义类 。 而从iOS 11来的时候,我的导航栏开始向上移动时,一个模态呈现的控制器被解雇。
然后,我发现sizeThatFits
只触发一次,当栏被初始化在iOS 11,这是防止改变自定义大小。
我所要做的就是在我的自定义类的Y偏移量栏中添加一个var,并根据系统版本进行设置:
如果在Objective C中为系统版本定义macros,否则使用if #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]; }
这就是一切都完美的作品, 这是明确你的导航栏是明确的颜色