[SwiftUI] Custom Bottom Drag Sheet 만들기

양재현·2025년 1월 28일
post-thumbnail

Bottom Drag Sheet를 커스텀으로 만들게 된 이유

당근마켓 앱이나 여러 앱을 보면 탭뷰 뒤에 바텀 드래그 시트가 존재한다. 나는 이런 UI를 만들고 싶었고 여러가지 시도를 해봤다.

1. .sheet() 모디파이어를 이용
-> 탭뷰 뒤에 바텀시트를 두고 싶었지만 아무리 해도 바텀시트가 탭뷰 앞으로 와서 탭뷰를 계속 가리게 되는 이슈가 있었다.

2. UIKit를 활용해 커스텀 뷰 생성
-> 이 방법은 정상적으로 작동했지만 나는 UIKit가 아니라 SwiftUI를 선호하기 때문에 기각했다.

3. BottomSheet 패키지 이용
-> 나와 같은 문제를 겪는 사람이 많았는지 패키지로 만들어 놓은 사람이 있었고 이걸 활용하여 만들었으나 뭔가 이끌림이 없어서 기각했다.

스택오버 플로우, 블로그 등을 잔뜩 찾아 봤지만 어떤 글에서도 해결할 수가 없어서 결국 SwiftUI에서 커스텀 뷰를 만들어보기로 했다.

Custom Bottom Drag Sheet 코드

1. BottomDragSheet 만들기

import SwiftUI

struct BottomDragSheet: View {
    @State private var currentOffset: CGFloat = 0
    @State private var endOffset: CGFloat = 0
    
    let sheetHeight: CGFloat = 120 //바텀 시트 높이 설정
    let sensitivity: CGFloat = 2 //드래그 민감도 (1이 기본, 0.5는 덜 민감, 2는 더 민감)
    
    var body: some View {
        ZStack {
            GeometryReader { geometry in
                let sheetPosition = geometry.size.height - sheetHeight
                VStack {
                    Capsule()
                        .fill(Color.gray)
                        .frame(width: 80, height: 4)
                        .padding(.top)
                    
                    ScrollView{} //여기 바텀시트안에 넣고 싶은 뷰 넣기
                }
                .frame(maxWidth: .infinity)
                .background(Color.white.cornerRadius(30))
                .offset(y: sheetPosition + currentOffset)
                .gesture(
                    DragGesture()
                        .onChanged { value in
                            withAnimation(.spring()) {
                                currentOffset = value.translation.height * sensitivity + endOffset
                            }
                        }
                        .onEnded { value in
                            withAnimation(.spring()) {
                                // 큰창
                                if currentOffset <  -sheetPosition / 2 {
                                    currentOffset = -sheetPosition
                                    // 중간 창
                                } else if currentOffset < -100 {
                                    currentOffset = -sheetPosition / 2
                                    // 작은 창
                                } else {
                                    currentOffset = 0
                                }
                                endOffset = currentOffset
                            }
                        }
                )
            }
        }.edgesIgnoringSafeArea(.bottom)
    }
    
}

#Preview {
    ZStack {
        Color.gray.ignoresSafeArea()
        BottomDragSheet()
    }
}
  • sheetHeight, sensitivity 변수를 두어 바텀시트의 높이 조절과 드래그 민감도를 조정할 수 있게 만들었다.
  • ScrollView{} 안에는 바텀시트 안에 넣고싶은 뷰를 넣으면 된다.
  • DragGesture()을 통해 드래그를 가능하게 만들었고, 원래 .sheet() 모디파이어에서 small, medium, large 사이즈로 바텀시트를 펼칠 수 있었던 것을 커스텀으로 구현했다.
  • .edgesIgnoringSafeArea(.bottom)으로 드래그를 올렸을때 시트가 올라가면서 밑에 부분이 보이는 것을 방지했다.

2. ZStack 으로 Map과 감싸주기

import SwiftUI
import MapKit

struct FirstView: View {
    var body: some View {
        ZStack {
            Map()
            BottomDragSheet()
        }
    }
}
  • 당근마켓처럼 Map위에다가 올리고 싶어서 ZStack으로 감싸줬다.

3. TabView에 적용

import SwiftUI

struct TabViewBottomSheet: View {
    @State private var selection = 1
    
    var body: some View {
        TabView(selection:$selection) {
            FirstView()
                .tabItem {
                    Image(systemName: "house")
                }.tag(1)
            SecondView()
                .tabItem {
                    Image(systemName: "list.bullet")
                }.tag(2)
            MyPageView()
                .tabItem {
                    Image(systemName: "person")
                }.tag(3)
        }
    }}
  • 이렇게 적용하면 탭뷰뒤에 바텀 드래그 시트를 놓을 수 있다.

이제 앱을 개발하다가 TabView 뒤에 Bottom Drag Sheet를 넣고 싶을때 이 코드를 활용 해야겠다.

0개의 댓글