web-dev-qa-db-ja.com

iOS 11ナビゲーションバーの高さのカスタマイズ

IOS 11では、sizeThatFitsメソッドはUINavigationBarサブクラスから呼び出されません。 UINavigationBarのフレームを変更すると、グリッチと誤ったインセットが発生します。だから、今navbarの高さをカスタマイズする方法はありますか?

Apple開発者によると( herehere 、および here を参照)、iOS 11でのナビゲーションバーの高さの変更はサポートされていません。 ここ ナビゲーションバーの下(ただしその外側)にビューを表示するなどの回避策を実行し、ナビゲーションバーの境界線を削除することを提案します。その結果、ストーリーボードにこれが表示されます。

enter image description here

デバイスでは次のようになります。

enter image description here

これで、他の回答で提案された回避策を実行できます:UINavigationBarのカスタムサブクラスを作成し、それにカスタムの大きなサブビューを追加し、sizeThatFitslayoutSubviewsをオーバーライドしてから、ナビゲーションのトップコントローラーのadditionalSafeAreaInsets.topを差分customHeight - 44pxに設定しますが、視覚的にはすべてが完璧に見えますが、ビューは依然としてデフォルトの44ピクセルです。私はsetFrameをオーバーライドしようとしませんでしたが、おそらく動作します。ただし、Apple開発者が上記のリンクのいずれかで書いたように: "...どちらもナビゲーションのフレームを変更しません[サポート] UINavigationControllerが所有するバー(Navigation Controllerは、それが適切であると判断すると、フレームの変更を喜んで踏みます)。 "

私の場合、上記の回避策により、ビューは次のようになりました(境界線を表示するデバッグビュー)。

enter image description here

ご覧のとおり、視覚的な外観は非常に良好で、additionalSafeAreaInsetsがコンテンツを正しくプッシュし、大きなナビゲーションバーが表示されますが、このバーにはカスタムボタンがあり、標準の44ピクセルのナビゲーションバーの下にある領域のみがクリック可能(画像の緑色の領域)。標準のナビゲーションバーの高さより下のタッチは、カスタムサブビューに到達しないため、ナビゲーションバー自体のサイズを変更する必要がありますが、Appleの開発者はサポートされていないと言います。

28
frangulyan

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

カスタム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'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 hereenter image description here

黄色はbarbackgroundViewです。黒い不透明度はBarContentViewです。

そして、BarContentViewのbackgroundColorを削除しました。

enter image description here

それでおしまい。

21
Shawn Baek

追加:この問題はiOS 11 beta 6で解決されたため、以下のコードは役に立たない^ _ ^


元の回答:

以下のコードで解決しました:

(statusBarの非表示がtrueであるかどうかに関係なく、常に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
9
CharlieSu

これは私のために働く:

- (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];
        }
    }
}
8
Minster.Zo

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

-layoutSubviews-setFrame:をオーバーライドするとともに、ナビゲーションバーのサイズを変更してコンテンツを非表示にしたくない場合は、新しく追加されたUIViewControllerのadditionalSafereaInsetsプロパティ( Apple Documentation )をチェックアウトする必要があります。

4
Reggian

ベータ4では修正されていますが、ナビゲーションバーの背景画像は実際のビューに合わせて拡大縮小されていないようです(ビュー階層ビューアーで確認することで確認できます)。現時点での回避策は、カスタムlayoutSubviewsUINavigationBarをオーバーライドしてから、次のコードを使用することです。

- (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のオフセットがあるため、上記の計算ではそれが追加されます。

4
strangetimes

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

これは私が使用するものです。 UISearchBarをタイトルまたはバーコンテンツのサイズを変更する他のビューとして使用する場合、通常のコンテンツ(44.0 px)で機能します。それに応じて値を更新する必要があります。ある時点でブレーキがかかる可能性があるため、これは自己の責任において使用してください。

これは、高さ90.0ピクセルのハードコードされたnavbarであり、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")
    }

}
2
Jelly

UINavigationBarをサブクラス化し、sizeThatFitsを使用して高さをオーバーライドすることにより、デフォルトのナビゲーションコントロールの上にステータスアイコンの行を追加できるように、ナビゲーションバーの高さを2倍にしました。幸いなことに、これには同じ効果があり、副作用が少ないシンプルなものです。 iOS 8〜11でテストしました。これをView Controllerに追加します。

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

これは通常のナビゲーションバーに適しています。 LargeTitleを使用している場合、titleViewのサイズは44ポイントの固定高さにならないため、これはうまく機能しません。ただし、通常のビューではこれで十分です。

@frangulyanのように、AppleはnavBarの下にビューを追加し、細い線(影の画像)を非表示にすることを提案しました。これが私が以下で思いついたものです。 uiviewをnavigationItemのtitleViewに追加し、そのuiview内にimageViewを追加しました。細い線(影の画像)を削除しました。追加したuiviewは、 navBarとまったく同じ色 です。そのビュー内にuiLabelを追加しました。

enter image description here

これが3D画像です。拡張ビューは、navBarの下のusernameLabelの背後にあります。灰色で、その下に細い線があります。 collectionViewまたは薄いseparatorLineの下にあるものを固定します。

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'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
    }
}
0
Lance Samaria