사이드 바 메뉴 구현

SteadySlower·2022년 12월 8일
0

이번 포스팅에서는 사이드 바 메뉴 만들기를 해보도록 하겠습니다. 이번 포스팅이 완전한 사이드 바 메뉴는 아니지만 일단 최소한의 기능을 가지고 있는 사이드 바 메뉴를 만들어 보도록 하겠습니다.

예전에 개인 프로젝트에 사이드 바 메뉴를 활용한 적이 있는데요. 그 때는 외부 라이브러리를 활용해서 구현을 했습니다. 이번에는 직접 구현해보고자 합니다.

UI 구현하기

사이드 메뉴 View 구현하기

보통의 사이드 바 메뉴를 보면 측면에서 사이드 바 메뉴가 나오면 나머지 화면은 어두워지는데요. 이 View를 구현하기 위해서 ZStack을 활용해서 반투명한 검은색을 깔고 앞에 사이드 바 메뉴가 오도록 했습니다.

그리고 사이드 바 메뉴는 화면의 70%만 차지하도록 했습니다. 그리고 ZStack의 alignment를 활용해서 다음 사이드 바 메뉴가 우측에서 보이도록 했습니다.

private struct SettingSideBar: View {
	var body: some View {
		ZStack(alignment: .trailing) {
		    Color.black.opacity(0.5)
		    VStack {
					// 사이드바 메뉴 내용
		    }
		    .frame(width: Constants.Size.deviceWidth * 0.7)
		    .background { Color.white }
		}
	}
}

원래 View에 사이드 메뉴 추가하기

원래 View에 사이드 메뉴를 추가할 때도 ZStack을 사용합니다. 사이드 메뉴가 보일 때는 기존의 View를 덮고 보여져야 합니다. 그리고 @State 변수를 하나 추가해서 사이드 메뉴가 보일지 말지를 결정하는 Bool 값을 만들어 놓도록 하겠습니다.

@State private var showSideBar: Bool = false

var body: some View {
	ZStack {
	    ScrollView {
	        LazyVStack(spacing: 32) {
	            ForEach(viewModel.words, id: \.id) { word in
	                WordCell(word: word, frontType: viewModel.frontType, eventPublisher: viewModel.eventPublisher)
	                    .frame(width: deviceWidth * 0.9, height: word.hasImage ? 200 : 100)
	            }
	        }
	    }
	    if showSideBar {
	        SettingSideBar()
	    }
	}
.toolbar {
      ToolbarItem {
          HStack {
              Button("랜덤") {
                  viewModel.shuffleWords()
              }
              Button("설정") {
                  showSideBar = true
              }
          }
      }
  }
}

드래그 제스쳐 추가

사이드 바 메뉴들은 보통 드래그를 통해서 닫을 수 있습니다. 지금 만드는 사이드 바 메뉴는 우측에서 위치하므로 좌에서 우로 드래그 하며 닫히도록 만들겠습니다.

아래 기능을 위해서 @GestureState를 사용했는데요. 예전에도 포스팅한 적이 있는 기능입니다. 자세한 구현 방법은 이 포스팅을 참고해주세요. @GestureState와 offset을 활용해서 사이드 바 메뉴를 움직이도록 했습니다.

그리고 마지막으로 onEnded를 통해서 드래그한 거리가 화면의 35% 즉 사이드 바 메뉴의 반 이상이라면 사이드 바 메뉴가 닫히도록 했습니다. 사이드 바 메뉴가 닫히려면 부모 View의 @State 변수를 false로 바꾸어야 합니다. 해당 변수를 @Binding으로 받아와서 false로 바꾸어 줍니다.

extension StudyView {
    private struct SettingSideBar: View {
        
        @Binding var showSideBar: Bool
        @GestureState private var dragAmount = CGSize.zero
        
        private var dragGesture: some Gesture {
            DragGesture(minimumDistance: 30, coordinateSpace: .global)
                .onEnded { onEnded($0) }
                .updating($dragAmount) { value, state, _ in
                    if value.translation.width > 0 {
                        state.width = value.translation.width
                    }
                }
        }
        
        var body: some View {
            ZStack(alignment: .trailing) {
                Color.black.opacity(0.5)
                    .onTapGesture { showSideBar = false }
                VStack {
                    Spacer()
                    Picker("", selection: $viewModel.studyMode) {
                        ForEach(StudyMode.allCases, id: \.self) {
                            Text($0.pickerText)
                        }
                    }
                    .pickerStyle(.segmented)
                    .padding()
                    Picker("", selection: $viewModel.frontType) {
                        ForEach(FrontType.allCases, id: \.self) {
                            Text($0.pickerText)
                        }
                    }
                    .pickerStyle(.segmented)
                    .padding()
                    Spacer()
                }
                .frame(width: Constants.Size.deviceWidth * 0.7)
                .background { Color.white }
                .offset(x: dragAmount.width)
                .gesture(dragGesture)
            }
            .ignoresSafeArea()
        }
        
        private func onEnded(_ value: DragGesture.Value) {
            if value.translation.width > Constants.Size.deviceWidth * 0.35 {
                showSideBar = false
            }
        }
    }
}

완성

아주 기본적인 기능만 가지고 있는 사이드 바 메뉴를 만들어 보았습니다. 결과물은 아래와 같습니다.

물론 다른 멋진 프레임워크들에 비하면 부족합니다만 그래도 구색은 갖춘 것 같습니다. 앞으로 등장 애니메이션, 모듈화 등의 추가적인 과제들을 구현해보도록 하겠습니다.

profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.

0개의 댓글