[Swift UIKit] 앱 전반에서 사용되는 Custom NavigationContoller
✅ 두 가지 스타일의 내비게이션 바 사용
현재 프로젝트에선 다음과 같은 구조로 내비게이션 바 스타일이 나뉩니다:
- 루트(첫 번째) 화면 → 로고 및 아이콘을 포함한 커스텀 스타일
- 그 이후 푸시된 화면들 → 시스템 스타일 기반의 일반 네비게이션 바
🎛 커스텀 네비게이션 컨트롤러에서 공통 처리
이미 앱 전반에서 사용되는 커스텀 내비게이션 컨트롤러(InteractivePoppableNavigationController)가 존재하므로, 여기에 스타일 설정을 통합합니다.
SceneDelegate 또는 AppDelegate에서 내비게이션 컨트롤러를 초기화할 수 있습니다:
// SceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = InteractivePoppableNavigationController(rootViewController: ViewController())
window?.makeKeyAndVisible()
}
// AppDelegate
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let navigationController = InteractivePoppableNavigationController()
self.navigationController = navigationController
window?.rootViewController = navigationController
//기타 코드는 생략
window?.makeKeyAndVisible()
return true
}
🔧 텍스트 없는 뒤로가기 버튼 만들기
홈에서 푸시되는 화면들에 적용할 기본 스타일부터 정리해 볼게요.
두 번째 화면부터는 보통 뒤로 가기 버튼(화살표)은 보이지만 텍스트는 존재하지 않습니다.
이 경우 다음과 같이 설정할 수 있습니다:
private var backButtonImage: UIImage? {
return UIImage.et_getImage(for: .backButton).withAlignmentRectInsets(
UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
)
}
private var backButtonAppearance: UIBarButtonItemAppearance {
let appearance = UIBarButtonItemAppearance()
appearance.normal.titleTextAttributes = [
.foregroundColor: UIColor.clear,
.font: UIFont.systemFont(ofSize: 0.0)
]
return appearance
}
이렇게 하면 뒤로 가기 버튼이 이미지로만 표현되어 깔끔한 디자인을 만들 수 있습니다.
여기에 inset 조정을 함께 사용하면 위치까지 세밀하게 컨트롤 가능합니다.
만약 본인이 사용하는 이미지의 기본 위치가 마음에 들고, 텍스트가 필요하다면 이 작업은 생략 가능합니다.
✨ 공통 스타일 설정 – setupNavigationBar()
private func setupNavigationBar() {
let appearance = UINavigationBarAppearance()
appearance.shadowColor = .clear // 기본으로 생기는 아래 그림자 라인 제거
appearance.setBackIndicatorImage(
backButtonImage, // 커스텀 뒤로가기 이미지
transitionMaskImage: backButtonImage
)
appearance.backButtonAppearance = backButtonAppearance // 텍스트 없는 버튼 적용
appearance.backgroundColor = .white // 배경색 설정
navigationBar.isTranslucent = false // 반투명 끄기
navigationBar.tintColor = .black // 버튼 및 텍스트 컬러
navigationBar.standardAppearance = appearance
navigationBar.compactAppearance = appearance
navigationBar.scrollEdgeAppearance = appearance
// standardAppearance 일반적인 상태에서 사용
// compactAppearance 화면이 작을 때 (예: 가로모드)
// scrollEdgeAppearance 스크롤이 상단에 있을 때 (투명/불투명 결정 등)
}
이런 식의 구체적인 설정을 가진 메서드를 설정하고 생성자에서 호출해 주면 됩니다.
문제는 루트 화면과 나머지 화면 간의 스타일이 다르다는 점이죠.
이럴 땐 UINavigationControllerDelegate의 willShow 메서드를 활용하면 됩니다:
그에 따른 처리는 NavigationController Delegate를 통해 가능합니다.
🔁 화면 전환 시 내비게이션 바 스타일 다르게 적용하기
extension InteractivePoppableNavigationController: UINavigationControllerDelegate {
public func navigationController(
_ navigationController: UINavigationController,
willShow viewController: UIViewController,
animated: Bool
) {
if viewController == viewControllers.first {
// 루트 화면 스타일 적용
setNavigationBarHidden(false, animated: animated)
setupRootNavigationBar()
} else {
// 일반 스타일 적용
setNavigationBarHidden(false, animated: animated)
setupNavigationBar()
}
}
}
Navgation은 Stack 기반이기 때문에 viewControllers.frist 속성을 통해 루트 여부를 쉽게 확인이 가능합니다.
화면이 전환될 때마다 willShow 델리게이트 메서드를 통해 현재 보여질 ViewController가 루트인지 아닌지 확인하고 스타일을 나눠 적용합니다
✅ 다수의 NavigationBarItem의 경우
private func setupRootNavigationBar() {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .et_brandColor2 // 커스텀 색상
appearance.shadowColor = .clear
navigationBar.standardAppearance = appearance
navigationBar.compactAppearance = appearance
navigationBar.scrollEdgeAppearance = appearance
navigationBar.isTranslucent = false
navigationBar.tintColor = .white
// 왼쪽 로고
topViewController?.navigationItem.leftBarButtonItem =
UIBarButtonItem(customView: titleLogoLabel)
// 오른쪽 버튼들 (검색, 알림)
let stackView = UIStackView(arrangedSubviews: [searchButton, notificationButton])
stackView.axis = .horizontal
stackView.spacing = 10
topViewController?.navigationItem.rightBarButtonItem =
UIBarButtonItem(customView: stackView)
}
1번 그림의 구현 코드입니다.
이 부분에선 BarButtotnItem이 두 가지 존재했던 점, 그 부분을 StackView를 통해 처리했습니다.
✍️ 마무리
- UINavigationControllerDelegate를 활용해 화면별로 스타일을 분기 처리할 수 있다.
- UINavigationBarAppearance를 통해 커스텀 스타일을 깔끔하게 적용할 수 있다.
- 루트 화면과 이후 화면의 UI/UX를 분리해 줌으로써 더 직관적인 사용자 경험을 제공할 수 있다.