김경록의 앱 개발 여정

[Swift UIKit] 앱 전반에서 사용되는 Custom NavigationContoller 본문

Trouble Shooting

[Swift UIKit] 앱 전반에서 사용되는 Custom NavigationContoller

Kim Roks 2025. 4. 11. 13:01

 

✅ 두 가지 스타일의 내비게이션 바 사용

현재 프로젝트에선 다음과 같은 구조로 내비게이션 바 스타일이 나뉩니다:

  • 루트(첫 번째) 화면 → 로고 및 아이콘을 포함한 커스텀 스타일
  • 그 이후 푸시된 화면들 → 시스템 스타일 기반의 일반 네비게이션 바

🎛 커스텀 네비게이션 컨트롤러에서 공통 처리

이미 앱 전반에서 사용되는 커스텀 내비게이션 컨트롤러(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를 분리해 줌으로써 더 직관적인 사용자 경험을 제공할 수 있다.

 

🙇‍♂️ 도움이 된 링크

https://ios-development.tistory.com/697