Swift: Custom Navigation Bar 만들기

Heechan
HcleeDev
Published in
7 min readFeb 11, 2021
Photo by Tim Trad on Unsplash

그냥 기본으로 제공하는 NavigationView를 사용하는게 편할 수는 있겠지만, 만들다 보면 Navigation Bar를 커스텀해야 하는 경우가 생긴다. 버튼을 여러개 넣고 싶다든가, 기본 NavigationView의 디자인을 참을 수 없다든가, 아니면 디자인팀의 요청이 있었다든가…

이번 글에서는 데모 앱을 만들면서 NavigationView를 조작했던 방법, 그리고 만들면서 마주쳤던 이런저런 문제점들을 어떻게 해결했는지 설명한다.

NavigationView와 NavigationLink

NavigationView는 SwiftUI에서 제공하는 가장 기본적인 화면전환을 위한 View다. NavigationView 안에 NavigationLink로 감싼 View를 넣어주면, 해당 View를 클릭할 때 설정된 destination으로 뷰가 옮겨간다.

아래 코드를 보자. 아래 코드에서는 NavigationView 내부에 HStack이 있고, 그 안의 Image를 NavigationLink가 감싸고 있다. 이 NavigationLink의 목적지는 SecondaryView로 설정되어있으니, 이 이미지를 클릭하면 SecondaryView로 넘어갈 것이다.

위 움짤에서 확인할 수 있듯 누르면 잘 넘어가진다.

이 NavigationLink는 NavigationView에 감싸져있다면 어느 정도 Depth에 위치해있건 상관 없다. NavigationView 안의 하위 뷰의 하위 뷰의 하위 뷰에 NavigationLink를 만들어둬도 잘 작동한다는 말이다. 예로 아래 코드는 위 움짤과 정확히 똑같이 기능한다.

하지만 위 움짤을 보면 왠지 모를 불편함이 느껴진다. 딱히 설정한 것도 없는데 HStack이 화면 중앙이 아닌 살짝 아래로 치우쳐 져있다는 느낌이 든다. 에라 모르겠다 하고 Spacer() 를 이용해 HStack을 화면 가장 위로 올려보내보았다.

하지만 가장 위까지 가지 않고 애매하게 올라가있는 모습이다. 저정도 위치면 가장 위의 상단바(safe area)가 가로막는 것도 아닌데, 왜 끝까지 올라가지 않을까?

우측 상단을 보면 UINavigationBar 뷰가 안보이지만 자리를 차지하고 있음을 확인할 수 있다.

그래서 Xcode의 기능인 Debug -> View Debugger -> Capture View Hierarchy를 이용해 메인 화면 가장 위에 올라와있는 뷰가 무엇인지 확인해보았다. (시뮬레이터에서 앱을 Run하는 중에만 실행할 수 있다)

사진을 확인해보면 알 수 있듯 우리의 HStack 보다 위에 자리를 차지하고 있는 애들이 있었고, 눌러서 우측 상단의 링크를 확인해보면 UINavigationBar라는 점을 알 수 있다. 여기선 나타나지 않았지만 edgesIgnoringSafeArea() 를 사용해서 HStack을 저 위로 보내버리면, 화면 계층 최상단의 NavigationBar가 이미지를 누르는 것을 가로막는다.

여튼 그래서 저게 필요하지 않고 오히려 방해만 되는 상황이라면 없애줘야 한다.

이렇게 NavigationView 내부에 있는 View에 modifier를 달아주면 된다. navigationBarHidden(true) 는 말 그래도 NavigationBar를 숨겨주는 것이고, navigationTitle("") 은 타이틀에 아무것도 넣지 않음으로서 타이틀이 차지하는 공간도 생기지 않도록 설정한 것이다.이렇게 설정하고 나서 앱을 실행해보면 아래와 같다.

가장 위 쪽에 HStack이 올라가있고 터치와 상호작용도 잘 되는 모습이다.

Navigation Bar

이제부터 살펴볼 것은 SecondaryView로 넘어갔을 때 등장하는 상단바다. 위 움짤을 보면 SecondaryView에서 다시 뒤로 돌아가기 위한 버튼이 상단에 있다는 점을 알 수 있다. 코드를 살펴봐도 저 버튼이 있는 상단바를 따로 만든 적은 없다. NavigationView에서 제공하는 기본 기능이라고 생각할 수 있다. 저기 있는 버튼은 BackButton이라고 불린다.

아무튼 버튼이 저렇게 있는게 마음에 안들어서, 우리 마음대로 Custom NavigationBar를 만들어보자.

일단 저기 있는 기본 버튼(상단바)을 없애줄 것이다. .navigationBarHidden(true) 를 SecondaryView에도 적용해준다. 그러면 상단바는 없어진다.

그 후 우리가 원하는 상단바를 만들어서 올려보자. 원래라면 더 이쁜 디자인을 적용하기 위해 하는 것이긴 하지만 지금은 일단 간단히 만들어보기 위해, 이미지를 하나 가져와서 상단바를 하나 만들어보았다.

참고로 Image(systemName: "") 에 들어가는 이미지 이름은 SF Symbols라는 프로그램을 다운받아서 확인할 수 있다.

나름 상단바 위치에 버튼이 생겼고, 클릭도 된다.

하지만 NavigationBar 역할을 하려면, 저 버튼을 눌렀을 때 뒤로가는 것이 되어야 할 것이다. 그러면 버튼의 action 에 적절한 행동을 넣어줘야 할 것이다.

여기서는 현재 상태, 환경을 알 수 있도록 도와주는 @Environment 프로퍼티 래퍼를 사용한다. 이 프로퍼티를 통해 현재 뷰가 보여지고 있는 상태인지 아닌지 알 수 있는 presentationMode에 접근할 수 있도록 할 수 있다.

@Environment(\.presentationMode) var presentationMode

이렇게 presentationMode 환경 변수에 접근할 수 있도록 변수를 선언할 수 있고, 이 변수의 타입은 기본적으로 Binding<presentationMode> 가 된다.

이렇게 Binding에 쌓여져 있는 값에 .dismiss() 메서드를 호출하면 현재 보여지고 있는 SecondaryView를 그만 보이고 이전으로 돌아간다.

성공적으로 NavigationView와 Custom Navigation Bar를 이용해 화면 전환을 이루어냈다.

드래그로 뒤로 가기

혹시 만약 버튼말고 화면을 드래그해서 뒤로 갈 수 있는 기능도 구현하고 싶을 경우도 방법이 있다. 뷰 왼쪽에서부터 오른쪽으로 얼마나 움직였는지 확인하고 위에서 사용한 .dismiss() 메서드를 활용할 수 있다.

.gesture modifier를 이용해 제스처가 x 방향으로 얼마나 움직였는지 고려해 화면 전환을 수행하도록 할 수 있다.

여기서 의외로 중요한건 .contentShape(Rectangle()) 인데, 이게 있어야 VStack 전체를 사각형 구역으로 인지하기 때문에 먼저 설정해주어야 화면 어디에서든 제스처를 인식할 수 있다.

꽤 기능적인 NavigationView가 완성되었다.

결론

NavigationView는 화면과 화면 사이를 쉽게 움직일 수 있도록 도와주는 기능이다. 요즘에는 State 프로퍼티 래퍼와 TabView 등을 함께 이용한 화면 전환 방식도 많이 사용되는데, NavigationView와는 또 기능이 조금 다르기 때문에 완전히 대체할 수는 없다.

지금은 간단한 예제라 괜찮긴 하지만, 앱 사이즈가 꽤 커지거나 하면 NavigationView 계층을 잘 따져서 사용해야 한다. 잘못하면 NavigationView안에 Navigation이 하나 더 생기거나 하는… 추한 모습을 보일 수 있으니 주의하면서 활용해야 한다.

--

--

Heechan
HcleeDev

Junior iOS Developer / Front Web Developer, major in Computer Science