[Swift] UINavigationBarController의 NavigationBar 높이 변경

소개



UINavigationController를 사용할 때 UINavigationBar의 높이를 변경하고 싶을 수있었습니다.
UINavigationController 와 UINavigationBar 를 커스터마이즈 해 보고 실용적인가를 해설해 가고 싶습니다.

실천



CustomNavigationController.swift 만들기



간단한 이니셜라이저를 만들어 CustomNavigationController를 편리하게 사용할 수 있습니다.

CustomNavigationController.swift
class CustomNavigationController: UINavigationController {

    // 1. イニシャライザ
    override init(rootViewController: UIViewController) {
        super.init(rootViewController: rootViewController)
    }

    // 2. イニシャライザ
    override init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?) {
        super.init(navigationBarClass: navigationBarClass, toolbarClass: toolbarClass)
    }

    // イニシャライザ
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    // 必須イニシャライザ
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // 簡易イニシャライザ (1. 2. をまとめ便利にしたもの)
    convenience init(rootViewController:UIViewController , navigationBarClass:AnyClass?, toolbarClass: AnyClass?){
        self.init(navigationBarClass: navigationBarClass, toolbarClass: toolbarClass)
        self.viewControllers = [rootViewController]
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // 他にすることがある場合
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

CustomNavigationBar.swift 만들기



NavigationBar의 높이를 변경하려면 사용자 지정 클래스를 만들어야 합니다.sizeThatFits(_ size: CGSize) 는, 지정된 사이즈에 최적인 사이즈를 계산해 돌려주도록(듯이) 뷰에 요구하는 메소드이므로, 이하와 같이 NavigationBar 의 높이를 지정하면 기대대로 움직여 줍니다.
이 때 주의점이 있고 iPhone X 이후의 경우는 SafeArea 계산이 필요합니다.

또한 NavigationBarContentView의 위치가 어긋나므로 NavigationBar의 높이에서 NavigationBarContentView의 높이를 뺀 위치로 옮겨야 합니다.

CustomNavigationBar.swift
class CustomNavigationBar: UINavigationBar {
    // NavigationBar の高さ
    private let barHeight: CGFloat = 100

    // イニシャライザ
    override init(frame: CGRect) {
        super.init(frame: frame)

        self.commonInit()
    }

    // 必須イニシャライザ
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func commonInit() {
        // NavigationBar の色
        self.barTintColor = .white
    }

    // 1. 指定されたサイズに最適なサイズを計算して返すようにビューに要求
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        var newSize = super.sizeThatFits(size)
        var topInset: CGFloat = 0.0

        // iPhone X 以降 (SafeArea の高さを取得)
        if #available(iOS 11.0, *) {
            topInset = superview?.safeAreaInsets.top ?? 0
        }

        // NavigationBar の高さを設定
        newSize.height = barHeight + topInset

        return newSize
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        if #available(iOS 11.0, *) {
            for subview in subviews {
                let stringFromClass = NSStringFromClass(subview.classForCoder)

                // NavigationBar の高さを調整
                if stringFromClass.contains("UIBarBackground") {
                    let topInset: CGFloat = superview?.safeAreaInsets.top ?? 0
                    subview.frame = CGRect(origin: CGPoint(x: 0, y: -topInset), size: sizeThatFits(self.bounds.size))
                }

                // UINavigationBarContentView の位置を調整
                if stringFromClass.contains("UINavigationBarContentView") {
                    let y = (barHeight - subview.frame.height)
                    subview.frame.origin.y = y
                }
            }
        }
    }
}

NavigationBar의 높이를 변경할 수 있습니다.
SafeArea도 문제없이 계산할 수 있는 것 같습니다.



그러나 언뜻 보면 예상대로 보이지만 디버그 화면을 확인하면 문제점이있었습니다.
RootViewController의 View가 NavigationBar와 겹쳐져 있습니다.

이 문제를 개선하려면 SafeArea를 확장해야 합니다.



결과



SafeArea를 확장한 후 디버그 화면을 확인해도 View 중첩은 개선되지 않았습니다.
따라서 View 를 추가할 때 상위 제약을 SafeArea 로 지정하면 예상대로 움직여 주었습니다.
아마 SafeArea의 확장은 되어 있었지만 디버그 화면에서는 확인할 수 없었다.

RootViewController.swift
class RootViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // 背景色
        self.view.backgroundColor = .systemGreen
        // NavigationBar のタイトル
        self.title = "Test"

        // SafeArea の拡張
        if #available(iOS 11.0, *) {
            // NavigationBar の高さ - 元の NavigationBar の高さ
            additionalSafeAreaInsets.top = 100 - 44
        }

        // 赤い正方形 を追加
        self.view.addSubview(topView)

        // 制約を設定
        topView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
        topView.widthAnchor.constraint(equalToConstant: 100).isActive = true
        topView.heightAnchor.constraint(equalToConstant: 100).isActive = true
        topView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
    }
}



사이고에게



꽤 복잡한 내용이 되었기 때문에, 이해하기 어려운 부분도 있을지도 모릅니다만, NavigationBar 의 높이를 변경할 수 있었습니다.
하지만 문제점도 많아 별로 실용적이지 않은 것 같습니다.

아래에 문제점을 과도하게 쓸 때: 이것들을 개선하거나 해도 사용하고 싶다면 구현해 봐도 좋을지도 모릅니다.
  • 화면 전환시 NavigationBar의 높이가 일시적으로 되돌아갑니다
  • NavigationBarItem 의 titleView 에 UIButton 등을 추가했을 때, 탭 할 수 있는 범위에 한계가 있다
  • 이것은 공식적으로도 대응하지 않는 것 같고, 개선하는 것은 해킹적 요소가 강하다
  • 장래에 사용할 수 없게 될 가능성이 있다


  • 여기까지 봐 주셔서 감사합니다, 여러분의 배움의 도움이 되면 다행입니다.

    참고문헌


  • sizeThatFits(_:) | Apple Developer Documentation
  • 좋은 웹페이지 즐겨찾기