Подтвердить что ты не робот

Настройка высоты панели навигации iOS 11

Теперь в iOS 11 метод sizeThatFits не вызывается из подклассов UINavigationBar. Изменение рамки UINavigationBar вызывает сбои и неправильные вставки. Итак, какие-нибудь идеи, как настроить высоту навигатора сейчас?

4b9b3361

Ответ 1

По словам разработчиков Apple (смотрите здесь, здесь и здесь), изменение высоты панели навигации в iOS 11 не поддерживается. Здесь они предлагают сделать обходные пути, например, иметь вид под панелью навигации (но за ее пределами), а затем удалить границу панели навигации. В результате вы получите это в раскадровке:

enter image description here

на устройстве выглядит так:

enter image description here

Теперь вы можете сделать обходной путь, который был предложен в других ответах: создать пользовательский подкласс UINavigationBar, добавить к нему свое собственное большое подпредставление, переопределить sizeThatFits и layoutSubviews, а затем установить additionalSafeAreaInsets.top для верхнего контроллера навигации. с разницей customHeight - 44px, но по-прежнему будет отображаться полоса по умолчанию 44px, хотя визуально все будет выглядеть идеально. Я не пытался переопределить setFrame, может быть, он работает, однако, как написал разработчик Apple в одной из ссылок выше: "... и ни один [не поддерживается] не изменяет фрейм панели навигации, которая принадлежит UINavigationController (навигационный контроллер будет рад поменять ваши изменения кадра, когда сочтет это целесообразным). "

В моем случае описанный выше обходной путь сделал представления похожими на это (представление отладки для отображения границ):

enter image description here

Как видите, внешний вид довольно хороший, 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()

            }
        }


    }


}

Установить раскадровку

enter image description here

Set NavigationBar class

Установить пользовательский класс NavigationBar

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

Это.

Ответ 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 в это представление и вот оно.

enter image description here

Здесь 3d изображение. Расширенный вид находится за именем пользователя Label под navBar. Он серый и имеет тонкую линию под ним. Просто закрепите свой collectionView или что-нибудь под тонкой разделительной линией.

enter image description here

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 - ЯСНЫЙ ЦВЕТ