일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- reactorkit
- rxdatasources
- Tuist
- task cancel
- custombottomsheet
- swift concurrency
- task cancellation
- BFS
- custom ui
- RxSwift
- domain data
- swift dashed line
- UIKit
- swift bottomsheet
- paragraph style
- SWIFT
- swift 백준
- traits
- claen architecture
- swift custom ui
- 타임라인 포맷팅
- swift 점선
- DP
- coordinator
- tusit font 추가 방법
- identifiable
- uikit toast
- swift navigationcontroller
- button configuration
- custom navigation bar
- Today
- Total
김경록의 앱 개발 여정
[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를 분리해 줌으로써 더 직관적인 사용자 경험을 제공할 수 있다.
🙇♂️ 도움이 된 링크
'Trouble Shooting' 카테고리의 다른 글
[Swift UIKit] 점선 스타일 보더 적용하기(Dashed Border Style) (0) | 2025.04.16 |
---|---|
[Swift UIKit] Custom Bottom Sheet (0) | 2025.04.11 |
[Swift] 여러 상황에 대응하는 레이아웃 만들기(with SnapKit, update Layout) (0) | 2025.03.08 |
[Swift] Launch Screen에 Asset.xcassets 이미지가 적용되지 않는다면 (0) | 2025.02.01 |
[Swift UIKit] 비회원은 못보는 TableView Section, 어떻게 할까? (0) | 2025.01.24 |