SwiftUI로 무작정 만들기 2탄, 슬라이딩 사이드바 메뉴

해류·2022년 5월 29일
0
post-thumbnail

슬라이딩 사이드바를 만들어보자!

→ 기존에 슬라이딩 메뉴를 만들 때, 보통 위아래 화면 뷰를 겹쳐놓고 윗 하면을 옆으로 치우는 방식으로 만들곤 했다. 이번에는 메인 뷰와 사이드 뷰를 HStack으로 일렬로 배치하고 드래그와 버튼으로 열리도록 만들어 보았다.

드래그는 편의성을 위해 조금만 드래그해도 펼쳐 지도록 빌드했다. (처음엔 사이드바의 중앙을 넘으면 펼쳐지도록 했는데 실제 사용해보니 매우 불편했음)

결과물




주요 기능

  • TabVIew(.tag), Namespace(애니메이션 궤적 생성), CustomCorner(UIBezierPath: 원하는 모서리만 라운드 처리)

사이드 뷰 만들기

커스텀 코너

UIBezierPath를 이용해 커스텀 쉐잎을 그리고 .clipShape으로 모서리를 덧씌운다.

  • 원하는 부분의 모서리만 골라서 변형 가능
import SwiftUI

//Custom Corner Shapes
struct CustomCorners: Shape {
    
    var corners: UIRectCorner
    var radius: CGFloat
    
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}
//CustomCorners 사용
.clipShape(CustomCorners(corners: [.topRight, .bottomRight], radius: 12))

Namespace

  • 네임스페이스 선언은 상위 뷰에 @Namespace var namespace 선언하고
  • 실제 궤적이 그려질 메소드나 하위뷰에 var namespace: Namespace.ID
  • 연결 시켜줄 메소드에는 namespace: namespace
  • id별 궤적 생성 애니메이션 .matchedGeometryEffect(id: "TapEffect", in: namespace)를 버튼 배경에

상위 뷰

@Namespace var namespace
    var body: some View {
        ZStack(alignment: .leading) {
            Color.black.ignoresSafeArea()
            VStack {
                Text("SideView")
                    .font(.largeTitle)
                    .fontWeight(.heavy)
                    .foregroundColor(.white)
                    .padding(22)
                Spacer()
                //SideTapButtom
                VStack(spacing: 15) {
                    TapButton(image: "house", title: "Main", selectedTitle: $selectedTitle, namespace: namespace)
                    TapButton(image: "square.and.pencil", title: "Memo", selectedTitle: $selectedTitle, namespace: namespace)
                    TapButton(image: "trash", title: "Delete", selectedTitle: $selectedTitle, namespace: namespace)
                }
                Spacer()
            }
            .ignoresSafeArea(edges: .bottom)
        }
        .frame(width: 240)
    }

하위 뷰

struct TapButton: View {
    
    var image: String
    var title: String
    @Binding var selectedTitle: String
    var namespace: Namespace.ID
    
    var body: some View {
        Button(action: {
            //선택된 타이틀은 selectedTab 값이 된다
            //namespace 애니메이션을 넣기 위해서 애니메이션이 있어야한다.
            withAnimation(.spring()) {
                selectedTitle = title
            }
        }) {
            HStack(spacing: 20){
                Image(systemName: image)
                    .frame(width: 10)
                    .font(.system(size: 22, weight: .regular))
                Text(title)
                    .fontWeight(.semibold)
            }
        }
        .foregroundColor(.white)
        .padding(.vertical, 10)
        .padding(.leading, 20)
        .frame(maxWidth: 180, alignment: .leading)
        .background(
            ZStack {
                if selectedTitle == title {
                    Color(hue: 1.0, saturation: 0.0, brightness: 0.046)
                        //선택된 뷰에 배경
                        .opacity(selectedTitle == title ? 1 : 0)
                        //CustomCorners
                        .clipShape(CustomCorners(corners: [.topRight, .bottomRight], radius: 12))
                        //id별 궤적 생성 애니메이션
                        .matchedGeometryEffect(id: "TapEffect", in: namespace)
                }
            }
        )
    }
}

홈 뷰 만들기

탭 뷰

  • 버튼을 만들 때, 선택된 버튼의 타이틀이 변수 selectedTab이 되도록하고 selection에 $selectedTitle로 연결하여 버튼이 선택될때 홈뷰의 화면이 .tag값을 따라 가도록 설정.
struct HomeView: View {
    
    @Binding var selectedTitle: String
    
    var body: some View {

        TabView(selection: $selectedTitle ) {
            MainView()
                .tag("Main")
            MemoView()
                .tag("Memo")
            DeleteView()
                .tag("Delete")
        }
        .frame(width: getRect().width)
    }
}

버튼

Button(action: {
            //선택된 타이틀은 selectedTab 값이 된다
            //namespace 애니메이션을 넣기 위해서 애니메이션이 있어야한다.
            withAnimation(.spring()) {
                selectedTitle = title
            }
        })

tabBar 제거 : TabView를 사용한 HomeView가 들어가는 상위 뷰에 넣어줘야함.

	//TabView를 사용하면(스크롤 미사용시) 보이지 않는 페이지버튼이 하단에 TabBar로 생성된다. 이것을 제거하는 초기화 코드
    init() {
        UITabBar.appearance().isHidden = true
    }

번외

DragGesture

  • 1탄에도 사용한 드래그 제스쳐, 드래그에 따라 offset이 따라가도록 만들었다.
  • 버튼으로 showSide의 값이 변화할 때, 변화를 감지해서 작동하도록 만들었다. .onChange(of: showSide)
    //사이드뷰 버튼용 변수
    @State var showSide = false
    //Sliding을 위한 변수
    @State var translation: CGSize = .zero
    @State var offsetX: CGFloat = -120

	   	 //홈과 사이드 메뉴가 움직이도록 하는 오프셋
        .offset(x: (translation.width + offsetX) > -120 ? ((translation.width + offsetX) < 120 ? translation.width + offsetX : 120 ) : -120)
        //사이드메뉴 버튼으로 showSide값에 변화가 있을 때, 사이드 메뉴 열기
        .onChange(of: showSide) {_ in
            withAnimation(.spring()) {
                if showSide && offsetX == -120 {
                    offsetX = 120
//                    print("showSide ON")
                }
                if !showSide && offsetX == 120 {
                    offsetX = -120
//                    print("showSide OFF")
                }
            }
        }
        //드래그로 사이드 메뉴 열기
        .gesture(
            DragGesture()
                .onChanged { value in
                    translation = value.translation
                }
                .onEnded {_ in
                    withAnimation(.spring()) {
                        let dragOffset = translation.width + offsetX

                        if dragOffset > -100 && offsetX == -120 {
                            offsetX = 120
                            showSide = true
                        } else if dragOffset < 100 && offsetX == 120 {
                            offsetX = -120
                            showSide = false
                        }
                        translation = .zero
                    }
                }
        )
        
    }
}




전체코드 : https://github.com/dduruge/SwiftUI_Test/tree/master/Swiftui/SlidingMenuNTabView

profile
iOS 개발자 지망생

0개의 댓글