Swift/Swift 개발 노트

[iOS/SwiftUI] TCA Sheet 구현하기

힛해 2024. 8. 20. 16:31
728x90

https://developer.apple.com/documentation/swiftui/view/sheet(ispresented:ondismiss:content:)

 

sheet(isPresented:onDismiss:content:) | Apple Developer Documentation

Presents a sheet when a binding to a Boolean value that you provide is true.

developer.apple.com

 

TCA 구조로 만들어진 프로젝트에서 Sheet 를 구현해보자!

 

nonisolated
func sheet<Content>(
    isPresented: Binding<Bool>,
    onDismiss: (() -> Void)? = nil,
    @ViewBuilder content: @escaping () -> Content
) -> some View where Content : View

 

바인딩중인 Bool 값이 참일때 시트를 표시한다.

isPresented

수정자의 클로저에서 만든 시트를 표시할지 여부를 결정하는 부울 값에 대한 바인딩입니다 content.

onDismiss

시트를 닫을 때 실행할 닫힘.

content

시트의 내용을 반환하는 클로저.

 

기본적인 사용방법

struct ParentView : View {
	@State var store: StoreOf<HomeFeature>
	var body : some View {
    	VStack{
        	Text("홈")
        }
         .sheet(isPresented: $store.falg, onDismiss: didDismiss) {
                    // 이 뷰가 모달로 표시됩니다.
                    ChildView(store: store)
                        .presentationDetents([.medium, .large])
                        .presentationCompactAdaptation(.none)
                }
    }
    
    func didDisMiss() {
    	// Sheet가 닫힐때 실행되는 함수
    }
}

 

나는 TCA 방식으로 만들어

enum Action: BindableAction {
        case openDecoration
        case closeDecoration
        case binding( BindingAction < State >)
    }
    
    var body : some ReducerOf <Self> {
        BindingReducer ()
        Reduce { state, action in
        
            switch action {
            case .binding(\.decoration) :
                print("click deco : ", state.decoration)
                return .none
            case .binding( _ ):
               return .none
            case .openDecoration:
                state.decoration = true
                return .none
            case .closeDecoration:
                state.decoration = false
                return .none

 

이렇게 만들고 didDismiss 함수안에 closeDecoration Action을 선언해 두었는데

 

func didDismiss() {
        store.send(.closeDecoration)
    }

 

closeDecoration Action이 실행되지 않는데도 정상적으로 작동하는 걸 확인했다.

 

그 이유는 sheet가 닫힐때 자동으로 바인딩 Bool 값이 toggle되어 Bool 값 toggle 메서드를 넣어주지 않아도 자동으로 toggle되는 것을 확인할 수 있다.

 

Presentation 설정

두가지가 대표적으로 사용된다.

  1. presentationDetents
  2. presentationCompactAdaptation

 

1. PresentationDetents

https://developer.apple.com/documentation/swiftui/view/presentationdetents(_:)

 

presentationDetents(_:) | Apple Developer Documentation

Sets the available detents for the enclosing sheet.

developer.apple.com

 

SwiftUI에서 시트를 표시할 때, 그 시트의 높이를 결정하는 "detent"를 설정하는 것을 의미한다.

 

nonisolated
func presentationDetents(
    _ detents: Set<PresentationDetent>,
    selection: Binding<PresentationDetent>
) -> some View

 

Parameter

detents

 

  • 시트가 멈출 수 있는 높이의 집합입니다.
  • 여러 개의 detent를 제공하면 사용자는 시트를 드래그하여 크기를 조정할 수 있습니다.
  • 예를 들어, 화면의 절반 높이 또는 전체 화면 높이에서 시트가 멈출 수 있게 설정할 수 있습니다.

selection

  • 현재 선택된 detent를 나타내는 Binding입니다.
  • 이 값은 detents 매개변수로 제공한 detent 중 하나와 일치해야 합니다.
  • 이를 통해 프로그래밍적으로 시트의 높이를 제어하거나 변경할 수 있습니다.

 

사용예시

struct ContentView: View {
    @State private var showSettings = false
    @State private var settingsDetent = PresentationDetent.medium


    var body: some View {
        Button("View Settings") {
            showSettings = true
        }
        .sheet(isPresented: $showSettings) {
            SettingsView()
                .presentationDetents(
                    [.medium, .large],
                    selection: $settingsDetent
                 )
        }
    }
}

 

 

 

detents 값은 시트를 올리고 내릴때 고정되는 크기를 의미하고

 

selection은 처음 열렸을때 보이는 크기를 지정한다. 생략시 detents값중 처음에 온 값으로 설정된다!

 

그런데 detents 값을 하나만 선택했을때는 sheet 상단의 하얀색 바는 보이지 않게 된다.

.presentationDetents([.medium])
.presentationCompactAdaptation(.none)

 

 

 

 

.presentationDetents([.medium, .large])
.presentationCompactAdaptation(.none)

 

 

 

 

더보기

 

  • func presentationDetents(Set<PresentationDetent>, selection: Binding<PresentationDetent>) -> some View:
    • 이 함수는 시트가 멈출 수 있는 높이, 즉 "detent"를 설정합니다.
    • 여러 detent를 지정할 수 있으며, 현재 선택된 detent를 프로그래밍적으로 제어할 수 있습니다.
    • 예를 들어, 시트를 화면의 절반 높이로 설정하거나 전체 화면을 덮도록 설정할 수 있습니다.
  • func presentationContentInteraction(PresentationContentInteraction) -> some View:
    • 이 함수는 시트에서 스와이프 제스처의 동작을 설정합니다.
    • 사용자가 시트를 드래그하거나 스와이프할 때 시트가 어떻게 반응할지 결정합니다.
  • func presentationDragIndicator(Visibility) -> some View:
    • 이 함수는 시트 상단에 표시되는 드래그 인디케이터의 가시성을 설정합니다.
    • 드래그 인디케이터는 사용자가 시트를 드래그하여 닫거나 크기를 조절할 수 있다는 시각적 힌트를 제공합니다.
  • struct PresentationDetent:
    • 시트가 자연스럽게 멈추는 특정 높이를 나타내는 타입입니다.
    • 예를 들어, 시트가 화면의 절반 높이에서 멈추거나 전체 화면을 덮도록 설정할 수 있습니다.
  • protocol CustomPresentationDetent:
    • 계산된 높이로 사용자 정의 detent를 정의하는 프로토콜입니다.
    • 개발자가 특정 요구 사항에 맞게 시트의 높이를 커스터마이징할 수 있도록 합니다.
  • struct PresentationContentInteraction:
    • 프레젠테이션이 스와이프 제스처에 반응하는 방식을 설정하는 데 사용되는 동작입니다.
    • 예를 들어, 사용자가 시트를 드래그할 때 시트가 닫히는지, 아니면 다른 동작을 수행하는지 설정할 수 있습니다.

 

 

2. presentationCompactAdaptation(_:)

https://developer.apple.com/documentation/swiftui/view/presentationcompactadaptation(_:)

 

presentationCompactAdaptation(_:) | Apple Developer Documentation

Specifies how to adapt a presentation to compact size classes.

developer.apple.com

 

프레젠테이션 화면을 컴팩트 클래스크기에 맞추는 메서드다!

"가로 방향의 iPhone과 같이 수직으로 컴팩트한 환경에서는 시트 프레젠테이션이 자동으로 전체 화면 커버로 나타나도록 조정됩니다. 또는 수정자를 사용하여 이 동작을 재정"의합니다

 

사용예시

struct ContentView: View {
    @State private var showSettings = false


    var body: some View {
        Button("View Settings") {
            showSettings = true
        }
        .sheet(isPresented: $showSettings) {
            SettingsView()
                .presentationDetents([.medium, .large])
                .presentationCompactAdaptation(.none)
        }
    }
}

 

 

 

그외 비슷한 메서드

더보기

1. Sheet

  • func sheet<Item, Content>(item: Binding<Item?>, onDismiss: (() -> Void)?, content: (Item) -> Content) -> some View
    • 특정 항목(Item)을 데이터 소스로 사용하여 시트를 표시합니다. item 바인딩이 nil이 아닐 때 시트가 표시됩니다.
    • 매개변수:
      • item: 시트의 콘텐츠에 사용될 데이터 소스.
      • onDismiss: 시트가 닫힐 때 실행될 선택적인 클로저.
      • content: 시트의 콘텐츠를 정의하는 클로저.

2. Full-Screen Cover

  • func fullScreenCover<Content>(isPresented: Binding<Bool>, onDismiss: (() -> Void)?, content: () -> Content) -> some View
    • 주어진 불리언 값이 true일 때 화면을 최대한 덮는 모달 뷰를 표시합니다.
    • 매개변수:
      • isPresented: 모달 뷰가 표시될지 여부를 제어하는 바인딩.
      • onDismiss: 모달 뷰가 닫힐 때 실행될 선택적인 클로저.
      • content: 모달 뷰의 콘텐츠를 정의하는 클로저.
  • func fullScreenCover<Item, Content>(item: Binding<Item?>, onDismiss: (() -> Void)?, content: (Item) -> Content) -> some View
    • 특정 항목(Item)을 데이터 소스로 사용하여 화면을 최대한 덮는 모달 뷰를 표시합니다. item 바인딩이 nil이 아닐 때 모달 뷰가 표시됩니다.

3. Popover

  • func popover<Item, Content>(item: Binding<Item?>, attachmentAnchor: PopoverAttachmentAnchor, content: (Item) -> Content) -> some View
    • 특정 항목(Item)을 데이터 소스로 사용하여 팝오버를 표시합니다.
    • 매개변수:
      • item: 팝오버의 콘텐츠에 사용될 데이터 소스.
      • attachmentAnchor: 팝오버가 화면에 부착될 위치를 정의하는 앵커.
      • content: 팝오버의 콘텐츠를 정의하는 클로저.
  • func popover<Content>(isPresented: Binding<Bool>, attachmentAnchor: PopoverAttachmentAnchor, content: () -> Content) -> some View
    • 주어진 불리언 값이 true일 때 팝오버를 표시합니다.
  • func popover<Item, Content>(item: Binding<Item?>, attachmentAnchor: PopoverAttachmentAnchor, arrowEdge: Edge, content: (Item) -> Content) -> some View
    • 특정 항목(Item)을 데이터 소스로 사용하여 팝오버를 표시합니다. 팝오버의 화살표가 나타날 모서리(Edge)를 지정할 수 있습니다.
  • func popover<Content>(isPresented: Binding<Bool>, attachmentAnchor: PopoverAttachmentAnchor, arrowEdge: Edge, content: () -> Content) -> some View
    • 주어진 불리언 값이 true일 때 팝오버를 표시하며, 팝오버의 화살표가 나타날 모서리를 지정할 수 있습니다.

PopoverAttachmentAnchor

  • 팝오버가 화면에 부착될 위치를 정의하는 앵커를 나타냅니다. (ex. rect, point 등의 값이 있습니다.)

 

 

공식문서를 보면서 공부를 하니 더 디테일하게 사용하고 차이점을 알 수 있어서 좋은 것 같다.