Intro: The Navigation Controller

예고했던 Navigation Controller와 TabBar Controller의 커스터마이즈 중, Navigation Controller의 구조와 간단한 커스텀 방법을 나누겠습니다. Navigation Controller(이하 내비게이션 컨트롤러)는 거의 모든 iOS 앱에서 사용된다고 해도 과언이 아닌 자주 사용되며, 간결하지만 막강한 기능을 가진 컨트롤러입니다. 앞선 글에서 소개했듯, TabBar Controller와 함께 iOS의 양대 컨트롤러라고 불러도 대부분의 iOS 개발자들이 동의하리라고 생각합니다. 이번 글에서는 내비게이션 컨트롤러를 커스텀하는 방법을 소개하겠습니다.

01
Navigation Cotroller (출처: apple developer)



목차
1. Push, Pop 애니메이션 커스터마이징
2. Pop 제스처 사용하기, 사용하지 않기
3. Back 버튼 타이틀 숨기기
4. 상단 좌우의 버튼 추가하기
5. NavigationBar 숨기기, 보여주기
6. What’s NEXT?


1. Push, Pop 애니메이션 커스터마이징

Push, Pop 트랜지션 기능은 내비게이션 컨트롤러의 핵심적인 기능입니다. Stack에 다음 View Controller를 쌓으며 디스플레이하는 것이 Push, 이전의 View Controller로 되돌아가는 것이 Pop 액션입니다. Pop 액션에는 최초에 디스플레이됐던 View Controller로 돌아가는 Pop to Root 액션이 포함되어 있습니다.

Pop View Controller(animated)



이러한 액션에는 애니메이션이 포함됩니다. 대개 기본적으로 적용된 애니메이션을 사용하면 되지만, 어떤 이유로 애니메이션을 커스텀하고 싶은 경우가 생깁니다. 이럴 때는 UINavigationController를 상속하는 커스텀 클래스를 만들어서 커스텀할 수 있습니다. 물론 Extension 형식으로 함수를 작성할 수도 있습니다.

// UINavigationController를 상속하는 커스텀 클래스를 작성
class BRNavigationController: UINavigationController {
    
    // 애니메이션을 적용하는 함수를 작성
    func overrideAnimation() {
        //여기에서 커스텀 애니메이션을 작성합니다.
        let transition = CATransition()
        transition.duration = 0.3
        transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        transition.type = kCATransitionFade
        self.view.layer.add(transition, forKey: nil)
    }
    
    // popToRootViewController(animted)를 오버라이드
    override func popToRootViewController(animated: Bool) -> [UIViewController]? {
        print("Custom Animation Triggered")
        if(viewControllers.last!.isKind(of: PersonalViewController.self)) { // 커스텀 애니메이션을 사용할 ViewController의 케이스를 분기한다
            // 작성된 커스텀 애니메이션 트리거
            self.overrideAnimation() 
            //UINaivgationController의 Function을 그대로 반환
            return super.popToRootViewController(animated: false)
        } else { // 다른 모든 케이스의 경우 디폴트 애니메이션을 사용
            //UINavigationController의 Function을 그대로 반환
            return super.popToRootViewController(animated: animated)
        }
    }
}



위의 코드로 작성한 애니메이션 아래의 영상과 같이 동작합니다.

커스텀 Pop 애니메이션이 적용된 Navigation Controller



위와 같이 커스텀된 내비게이션 컨트롤러는, 단지 애니메이션을 오버라이드하는 데 그치지 않고 다양한 방식의 효율적 코드 작성을 할 수 있게 합니다. 우리가 아는 것처럼, 수퍼클래스의 위용과 유용을 마음껏 누릴 수 있습니다.


2. Pop 제스처 사용하기, 사용하지 않기

내비게이션 컨트롤러에서는 화면 왼쪽 끝에서 오른쪽으로 스와이프하는 Pop 제스처를 사용해 이전 View Controller로 돌아갈 수 있습니다. 하지만 종방향 스크롤이나 스와이프 이벤트를 사용하는 ViewController의 경우 어쩔 수 없이 Pop 제스처를 막아야 하는 일이 생깁니다. 이럴 때에는 해당하는 ViewController에서 다음과 같이 간단한 코드로 Pop 제스처를 방지하거나, 방지 해제할 수 있습니다.

// 아래의 코드를 트리거하면 Pop 제스처를 비활성화할 수 있습니다
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false



이 코드를 한 번 적용하면, 해당 내비게이션 컨트롤러의 Stack에 쌓인(또는 쌓일) View Controller에 일괄적으로 적용되기 때문에 반드시 다른 ViewController에서는 기본적으로 isEnabeld를 True값으로 지정하도록 코드를 구성하여 모든 ViewController에 일괄적용되는 것을 방지해야 합니다.

다만 이 부분에서 중요한 것은, Back 버튼을 숨기거나 커스텀할 때 각별히 주의해야 한다는 것입니다. 제스처를 사용하는 사용자들도 있지만, 제스처의 존재 자체를 모르는 사용자들도 있기 때문에 Back 버튼은 대부분의 경우 유지하는 것이 좋습니다. 제스처를 비활성화할 때는 더더욱 유지해야 하고요.

02
Back Button이 없다면 어떻게 뒤로 돌아갈 수 있을까요.



3. Back 버튼의 타이틀 숨기기

내비게이션 컨트롤러에 포함된 Navigation Bar(이하 내비게이션 바)의 Back 버튼은 자동으로 이전 ViewController의 타이틀을 보여주도록 디폴트 설정되어 있습니다. 이렇게 자동지정된 타이틀이 마음에 들지 않는다면, 간단한 트릭을 사용하여 타이틀을 없앨 수 있습니다.

먼저, Back 버튼의 타이틀이 되는 이전 ViewController의 타이틀은 ViewController에서 다음과 같이 지정됩니다.

// 직접 ViewController의 타이틀을 지정
viewController.title = "이것이 바로 타이틀입니다"



03
Back Button에 '상품정보' 타이틀이 보입니다.


위의 코드로 지정한 ViewController의 타이틀은 Push 액션을 통해 다음 ViewController로 넘어갔을 때 Back 버튼의 타이틀로 사용됩니다. 그래서 이 코드를 사용하지 않고, 커스텀 Label을 titleView에 넣어주는 것으로 대신할 수 있습니다.

// titleView로 사용할 Label을 생성
let label = UILabel(frame: customFrame)
label.text = "이것을 타이틀로 사용합니다"
// viewController의 titleView를 생성한 Label로 셋업
viewController.titleView = label



04
짜잔- Back Button의 타이틀이 사라졌습니다!



4. 상단 좌우 버튼 추가하기

여러 iOS 앱들을 사용하다 보면, 내비게이션 바의 좌/우측단에 위치한 버튼들을 자주 보게 됩니다. 이 버튼들은 BarButtons(이하 내비게이션 바 버튼) 라고 불리우는 컴포넌트들입니다. 내비게이션 바 버튼들은 배열 방식으로 좌/우측에 각각 배치됩니다. 원하는 이미지와 텍스트 등으로 내비게이션 바 버튼을 생성한 후, 좌/우측의 버튼 배열 중 원하는 곳에 각각 넣어주면 디스플레이 되는 방식입니다. 다음의 코드 예제를 통해 내비게이션 바 버튼을 추가할 수 있습니다.

// RightBarButtons에 추가할 UIBarButtonItem을 생성
let customButton = UIBarButtonItem(customView: customView)
// Container가 될 Array를 생성 (혹은 직접 지정하는 방법도 있습니다)
let rightBarButtons: [UIBarButtonItem] = []
// Array에 버튼 아이템을 추가
rightBarButtons.append(customButton)
// RightBarButtonItems 배열을 셋업
viewController.navigationItem.rightBarButtonItems = rightBarButtons
//LeftBarButtons에 추가할 UIBarButtonItem을 생성
let customButtonCopy = UIBarButtonItem(customView: customView)
// Container가 될 Array를 생성 (혹은 직접 지정하는 방법도 있습니다)
let leftBarButtons: [UIBarButtonItem] = []
// Array에 버튼 아이템을 추가
leftBarButtons.append(customButtonCopy)
// LeftBarButtonItems 배열을 셋업
viewController.navigationItem.leftBarButtonItems = leftBarButtons



05
타이틀뷰, LeftBarButton, RightBarButton이 모두 커스텀된 브랜디의 홈



5. NavigationBar 숨기기, 보여주기

앱의 UI가 전체화면으로 컨텐츠를 표시해야 할 때, 또는 다른 목적에 의해서 내비게이션 바를 숨기거나 보여주어야 할 때가 있습니다. 이럴 때는 간단한 코드 트리거로 내비게이션 바를 숨기거나 보여줄 수 있습니다.

// 단 한 줄의 코드로 내비게이션 바를 숨길 수 있다구요? 
navigationController.setNavigationBarHidden(false, animated: true)



내비게이션바를 숨겼다가 보였다가




6. What’s NEXT?

현재 앱스토어에 배포된 브랜디 iOS 앱은 내비게이션 컨트롤러를 적극적으로 활용하여 작성되었습니다. 내비게이션 컨트롤러는 기본 설정으로 사용할 때에도 여전히 막강한 특징들을 많이 가지고 있기 때문에, 선택적으로 알아두어야 할 컴포넌트가 아닌 필수적으로 그 장단점과 용법을 꿰고 있어야 하는 중요한 컴포넌트입니다. 내비게이션 컨트롤러만 잘 다루어도 앱을 개발할 때 굉장히 도움을 많이 받을 수 있다는 것이죠.

내비게이션 컨트롤러는 다양한 방식으로 커스터마이즈를 할 수도 있습니다. 물론 이러한 커스터마이즈는 필수사항은 아닙니다. 디자인적 요소를 적용하기 위해 커스터마이즈하는 경우가 대부분이지만, 그에 못지 않게 개발자가 프로젝트의 컴포넌트를 정규화하고 모듈화하기 위해 커스텀하는 경우도 많은 만큼 StackOverflow나 애플 개발자 문서를 참고해 다양한 커스터마이즈를 해보는 것도 재미있을 겁니다.

다음 글에서는 TabBar Controller의 커스터마이즈 방식에 대해 간략하게 공유하겠습니다. iOS 루키들의 장수와 번영을 바라며, 글을 마칩니다. Live long and prosper!


참고
UINavigationController - UIKit | Apple Developer Documentation


이정환 과장 | R&D 개발MA팀
leejh@brandi.co.kr
브랜디, 오직 예쁜 옷만