Теперь в iOS 11 метод sizeThatFits
не вызывается из подклассов UINavigationBar
. Изменение рамки UINavigationBar
вызывает сбои и неправильные вставки.
Итак, какие-нибудь идеи, как настроить высоту навигатора сейчас?
Настройка высоты панели навигации iOS 11
Ответ 1
По словам разработчиков Apple (смотрите здесь, здесь и здесь), изменение высоты панели навигации в iOS 11 не поддерживается. Здесь они предлагают сделать обходные пути, например, иметь вид под панелью навигации (но за ее пределами), а затем удалить границу панели навигации. В результате вы получите это в раскадровке:
на устройстве выглядит так:
Теперь вы можете сделать обходной путь, который был предложен в других ответах: создать пользовательский подкласс UINavigationBar
, добавить к нему свое собственное большое подпредставление, переопределить sizeThatFits
и layoutSubviews
, а затем установить additionalSafeAreaInsets.top
для верхнего контроллера навигации. с разницей customHeight - 44px
, но по-прежнему будет отображаться полоса по умолчанию 44px, хотя визуально все будет выглядеть идеально. Я не пытался переопределить setFrame
, может быть, он работает, однако, как написал разработчик Apple в одной из ссылок выше: "... и ни один [не поддерживается] не изменяет фрейм панели навигации, которая принадлежит UINavigationController (навигационный контроллер будет рад поменять ваши изменения кадра, когда сочтет это целесообразным). "
В моем случае описанный выше обходной путь сделал представления похожими на это (представление отладки для отображения границ):
Как видите, внешний вид довольно хороший, additionalSafeAreaInsets
правильно сдвинул контент вниз, видна большая панель навигации, однако у меня есть пользовательская кнопка на этой панели, и только область, которая идет под стандартной 44-пиксельной навигацией панель кликабельна (зеленая область на изображении). Касания ниже стандартной высоты панели навигации не достигают моего пользовательского подпредставления, поэтому мне нужно изменить размер самой панели навигации, что, по словам разработчиков Apple, не поддерживается.
Ответ 2
Обновлено 07 января 2018 года
Этот код поддерживает XCode 9.2, iOS 11.2
У меня такая же проблема. Ниже мое решение. Я предполагаю, что размер высоты равен 66.
Пожалуйста, выберите мой ответ, если он вам поможет.
Создать CINavgationBar.swift
import UIKit
@IBDesignable
class CINavigationBar: UINavigationBar {
//set NavigationBar 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()
}
}
}
}
Установить раскадровку
Установить пользовательский класс 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 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.
Это.
Ответ 3
Добавлено: Проблема решена в iOS 11 beta 6, поэтому код ниже бесполезен ^ _ ^
Оригинальный ответ:
Решится с кодом ниже:
(Я всегда хочу, чтобы navigationBar.height + statusBar.height == 64 был ли скрытый статусBar истинным или нет)
@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
Ответ 4
это работает для меня:
- (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];
}
}
}
Ответ 5
Упрощен с помощью 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
}
}
}
}
}
Ответ 6
Хотя он исправлен в бета-версии 4, кажется, что фоновое изображение навигационной панели не масштабируется с фактическим видом (вы можете проверить это, посмотрев на просмотр view-hierarchy viewer). Обходной путь на данный момент состоит в том, чтобы переопределить 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
, чтобы он отображался за панель состояния, поэтому приведенный выше расчет добавляет, что в.
Ответ 7
Наряду с переопределением -layoutSubviews
и -setFrame:
вы должны проверить недавно добавленное свойство UIViewController additionalSafereaInsets
(Apple Documentation), если вы не хотите, чтобы измененная панель навигации скрывала ваш контент.
Ответ 8
на 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)
)
)
Ответ 9
Это то, что я использую. Он работает для обычного контента (44.0 px), если вы используете UISearchBar
как заголовок или другие представления, которые изменяют размер содержимого панели, вы должны соответствующим образом обновить значения. Используйте это на свой страх и риск, так как в какой-то момент он может тормозить.
Это навигационная панель с жесткой кодировкой высотой 90.0px, работающая как на iOS 11, так и на более ранних версиях. Возможно, вам придется добавить некоторые вставки в UIBarButtonItem
для pre iOS 11, чтобы выглядеть одинаково.
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")
}
}
Ответ 10
Это хорошо работает для обычной панели навигации. Если вы используете LargeTitle, это не будет работать хорошо, потому что размер titleView не будет фиксированной высотой 44 точки. Но для обычного просмотра этого должно быть достаточно.
Например, @frangulyan apple предложила добавить вид под navBar и скрыть тонкую линию (теневое изображение). Это то, что я придумал ниже. Я добавил пользовательский вид в titleItem навигационного элемента, а затем добавил в него пользовательский вид. Я удалил тонкую линию (теневое изображение). Добавленный мною вид точно такой же, как у navBar. Я добавил uiLabel в это представление и вот оно.
Здесь 3d изображение. Расширенный вид находится за именем пользователя Label под navBar. Он серый и имеет тонкую линию под ним. Просто закрепите свой collectionView или что-нибудь под тонкой разделительной линией.
9 шагов описаны над каждой строкой кода:
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 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 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 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 background color to the same exact color as the navBar 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 anchors directly underneath the navigation bar
setExtendedViewAndSeparatorLineAnchors()
// 8. set the usernameLabel 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
}
}
Ответ 11
Я удваивал высоту своей панели навигации, поэтому я мог добавить строку значков состояния над навигационными элементами по умолчанию, путем подкласса UINavigationBar и использования sizeThatFits для переопределения высоты. К счастью, это имеет тот же эффект и проще, с меньшим количеством побочных эффектов. Я тестировал его с iOS 8 по 11. Поместите это в свой контроллер:
- (void)viewDidLoad {
[super viewDidLoad];
if (self.navigationController) {
self.navigationItem.prompt = @" "; // this adds empty space on top
}
}
Ответ 12
Большинство ответов выше могут решить отдельные проблемы или работать для многих людей, но то, что я нашел полезным, и решил мою проблему с несколькими строками кода, объясняется ниже.
В моем случае у меня был специальный класс для панели навигации и строка состояния скрыта. И с момента выхода iOS 11 мой навигационный бар начал двигаться вверх, когда моделизированный контроллер был уволен.
Затем я обнаружил, что sizeThatFits
запускается только один раз, когда в IOS 11 инициализируется строка, которая препятствует изменению настраиваемого размера.
Все, что мне нужно было сделать, это добавить var в мой пользовательский класс для Y Offset панели и установить его на основе версии системы, чтобы:
определить макрос для версии системы, если в Objective C использовать иначе, если #доступный
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];
}
Что тогда все работает отлично, ЭТО ОБРАЩАЕТ ВАШ БЛОК NAV - ЯСНЫЙ ЦВЕТ