본문 바로가기

iOS/TroubleShooting

UINavigationBar 커스텀

728x90

* Sopt 32기 앱잼을 하며 노션에 작성한 트러블 슈팅을 옮겨온 글입니다.

네비바 커스텀 장점

  • 뷰 계층구조 자동 관리
  • 앱 내에서 통일된 내비게이션 스타일을 유지하기 용이
  • 화면 회전 및 다양한 디바이스 크기에 쉽게 대응

원래는 **UIView**에 컴포넌트를 올리는 방식으로 네비바 커스텀을 진행했었는데요, 이러한 장점이 있다는 것을 알게 된 후 기존 **UIView**에 컴포넌트를 올리는 방식이 아닌 **UINavigationBar**을 커스텀하는 방식으로 전환하게 되었습니다.

그러나 예상보다 커스텀이 가능한 범위가 제한되어 있었고, 특히 네비바의 높이를 조정하는 과정에서 네비바가 view를 가리는 이슈가 발생했습니다.

import UIKit

final class PophoryNavigationController: UINavigationController {

    override func viewDidLayoutSubviews() {
        configureNavigationBar()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    func configureNavigationBar() {
        let titleFont = UIFont.head2
        let titleColor = UIColor.pophoryBlack
        lazy var defaultNaviBarHeight = { self.navigationBar.frame.size.height }()
        let newNaviBarHeight = defaultNaviBarHeight + 22

        var newFrame = self.navigationBar.frame
        newFrame.size.height = newNaviBarHeight
        navigationBar.frame = newFrame
        let titleAttributes: [NSAttributedString.Key: Any] = [.font: titleFont, .foregroundColor: titleColor]
        self.navigationBar.titleTextAttributes = titleAttributes

        navigationItem.title = navigationItem.title ?? "NavTitle"
    }
}
import UIKit

protocol NavigationConfigurator {
    func configureNavigationBar(in viewController: UIViewController, navigationController: UINavigationController?, showRightButton: Bool, rightButtonImageType: PophoryNavigationConfigurator.RightButtonImageType?)
}

final class PophoryNavigationConfigurator: NavigationConfigurator {

    static let shared = PophoryNavigationConfigurator()

    enum RightButtonImageType {
        case plus
        case delete
        case setting
    }

    private init() {}

    func configureNavigationBar(in viewController: UIViewController, navigationController: UINavigationController? = nil, showRightButton: Bool = false, rightButtonImageType: RightButtonImageType? = nil) {
        guard let navigationController = navigationController as? PophoryNavigationController else {
            return
        }

        navigationController.configureNavigationBar()

        if let rightButtonImageType = rightButtonImageType {
            let rightButton: UIBarButtonItem

            switch rightButtonImageType {
            case .plus:
                rightButton = UIBarButtonItem(image: ImageLiterals.myAlbumPlusButtonIcon, style: .plain, target: viewController, action: #selector(AlbumDetailViewController.addPhotoButtonOnClick))
            case .delete:
                rightButton = UIBarButtonItem(image: ImageLiterals.trashCanIcon, style: .plain, target: viewController, action: #selector(PhotoDetailViewController.deleteButtonOnClick))
            case .setting:
                rightButton = UIBarButtonItem(image: ImageLiterals.settingIcon, style: .plain, target: viewController, action: #selector(BaseViewController.rightButtonOnClick))
            }

            viewController.navigationItem.rightBarButtonItem = rightButton
            viewController.navigationItem.rightBarButtonItem?.tintColor = .pophoryBlack
        }

        let backBarButton = UIBarButtonItem(image: ImageLiterals.backButtonIcon, style: .plain, target: viewController, action: #selector(BaseViewController.backButtonOnClick))
        viewController.navigationItem.leftBarButtonItem = backBarButton
        viewController.navigationItem.leftBarButtonItem?.tintColor = .pophoryBlack

        let titleText = (viewController as? Navigatable)?.navigationBarTitleText ?? viewController.title ?? ""
        viewController.title = titleText
    }
}

익스텐션에 **UINagivationBar+statusBar의 높이를 더한 변수**를 선언했습니다.

var totalNavigationBarHeight: CGFloat {
            let navigationBarHeight = navigationController?.navigationBar.frame.size.height ?? 0
            let statusBarHeight = UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.size.height ?? 0
            return navigationBarHeight + statusBarHeight
        }
override func viewDidLoad() {
                super.viewDidLoad()

        view.addSubview(nameInputView)

        nameInputView.snp.makeConstraints { make in
            make.edges.equalTo(view.safeAreaInsets).inset(UIEdgeInsets(top: totalNavigationBarHeight, left: 0, bottom: 0, right: 0))
        }
    }

그러나 viewDidLayout에서 레이아웃을 설정할 경우, 설정한 navigationBar Height가 아닌 원래 뷰의 safeAreaLayoutGuide를 사용하게 됩니다. 이 문제는 멘토님의 도움으로 viewDidLayoutSubviews() 메서드에서 레이아웃 처리를 해야 설정한 마진값부터 처리된다는 것을 알게 되어 해결하게 되었습니다.

override func viewDidLayoutSubviews() {
        view.addSubview(nameInputView)

        nameInputView.snp.makeConstraints { make in
            make.edges.equalTo(view.safeAreaInsets).inset(UIEdgeInsets(top: totalNavigationBarHeight, left: 0, bottom: 0, right: 0))
        }
    }

이 경험을 통해 앞으로 다른 프로젝트에서는 디자인과 애니메이션 구현 여부 등을 고려하여 적절한 설계 방식을 선택해야 함을 느꼈습니다.