본문 바로가기

학습 노트/Swift UI (2022)

10 ~ 11. Sheet / FullScreenCover & Popover

Sheet / FullScreenCover


Sheet는 화면을 Modal 방식으로 표시한다.
Modal은 화면 전체를 채우는 Full Screen Modal과 화면의 일부를 채우는 Sheet, Card, Card Modal 방식이 있다.
Sheet는 화면에 표시될 때 아래에서 위로 올라오는 Present 방식으로 표시되고,
사라질 때 위에서 아래로 사라지는 Dismiss 방식으로 사라진다.

struct Nav_Sheet: View {
   @State private var statusMessage = ""
   @State private var sheetState = false

   var body: some View {
      VStack {
         Text(statusMessage)
            .font(.largeTitle)

         Button(action: {
             sheetState.toggle()
         }, label: {
            Text("Show Sheet")
         })
         .padding()
         .fullScreenCover(isPresented: $sheetState) {
             HStack {
                 Button {
                     statusMessage = "State 1"
                 } label: {
                     Text("Btn1")
                 }

                 Button {
                     statusMessage = "State 2"
                 } label: {
                     Text("Btn2")
                 }
             }
         }
      }
      .navigationBarTitle("Sheet")
   }
}
struct Nav_Sheet: View {
   @State private var statusMessage = ""
   @State private var sheetState = false

   var body: some View {
      VStack {
         Text(statusMessage)
            .font(.largeTitle)

         Button(action: {
             sheetState.toggle()
         }, label: {
            Text("Show Sheet")
         })
         .padding()
         .sheet(isPresented: $sheetState) {
             HStack {
                 Button {
                     statusMessage = "State 1"
                 } label: {
                     Text("Btn1")
                 }

                 Button {
                     statusMessage = "State 2"
                 } label: {
                     Text("Btn2")
                 }
             }
         }
      }
      .navigationBarTitle("Sheet")
   }
}

구현 패턴은 sheet나 fullScreen이나 alert와 유사하다.
State Variable을 하나 생성하고, 이를 전달하면 된다.

더보기

sheet(isPresented:onDismiss:content:)

 

Apple Developer Documentation

 

developer.apple.com

fullScreenCover(isPresented:onDismiss:content:)

 

Apple Developer Documentation

 

developer.apple.com

 

생성자 파라미터는 아래와 같다

  • isPresented
    State variable을 전달한다.
  • onDismiss
    closure로 구성되며 sheet와 fullScreen이 dismiss 되는 동안 진행될 작업을 구현한다.
  • content
    sheet의 구성을 진행한다.

fullScreen과 sheet 방식은 구현 방법은 동일하지만 정 반대의 사용성을 가진다.
sheet의 dissmiss 방식은 별도의 구현 없이 sheet를 위에서 아래로 쓸어내리는 제스처로 가능하도록 구현돼 있지만,
fullScreen은 별도의 구현 없이는 생성된 창을 숨길 수 있는 방법이 없다.

dismiss 구현

State Variable 토글

struct Nav_Sheet: View {
   @State private var statusMessage = ""
   @State private var sheetState = false


   var body: some View {
      VStack {
         Text(statusMessage)
            .font(.largeTitle)

         Button(action: {
             sheetState.toggle()
         }, label: {
            Text("Show Sheet")
         })
         .padding()
         .fullScreenCover(isPresented: $sheetState) {
             HStack {
                 Button {
                     statusMessage = "State 1"
                     sheetState.toggle()
                 } label: {
                     Text("Btn1")
                 }

                 Button {
                     statusMessage = "State 2"
                     sheetState.toggle()
                 } label: {
                     Text("Btn2")
                 }
             }
         }
      }
      .navigationBarTitle("Sheet")
   }
}

fullScreen이 숨으면서 이전의 화면이 나타난다.

dismiss사용하기

버전별로 두 가지 방식이 존재한다.

첫 번째는 presentationMode를 사용하는 것으로 iOS14까지의 방식이다.
두 번째는 첫 번째 방법의 개선된 방식으로 dismiss를 바로 사용할 수 있게 변경됐다. iOS15부터의 방식이다.

~iOS14

struct ContentView: View {
   @State private var statusMessage = ""
   @State private var sheetState = false

   var body: some View {
      VStack {
         Text(statusMessage)
            .font(.largeTitle)

         Button(action: {
             sheetState.toggle()
         }, label: {
            Text("Show Sheet")
         })
         .padding()
         .fullScreenCover(isPresented: $sheetState) {
             SheetView(statusMessage: $statusMessage)
         }
      }
      .navigationBarTitle("Sheet")
   }
}

struct SheetView: View {
    @Environment(\.presentationMode) var presentationMode
    @Binding var statusMessage: String

    var body: some View {
        HStack {
            Button {
                statusMessage = "State 1"
                presentationMode.wrappedValue.dismiss()
            } label: {
                Text("Btn1")
            }

            Button {
                statusMessage = "State 2"
                presentationMode.wrappedValue.dismiss()
            } label: {
                Text("Btn2")
            }
        }
    }
}

 

iOS15~

struct ContentView: View {
   @State private var statusMessage = ""
   @State private var sheetState = false

   var body: some View {
      VStack {
         Text(statusMessage)
            .font(.largeTitle)

         Button(action: {
             sheetState.toggle()
         }, label: {
            Text("Show Sheet")
         })
         .padding()
         .fullScreenCover(isPresented: $sheetState) {
             SheetView(statusMessage: $statusMessage)
         }
      }
      .navigationBarTitle("Sheet")
   }
}

struct SheetView: View {
    @Environment(\.dismiss) var dismiss
    @Binding var statusMessage: String

    var body: some View {
        HStack {
            Button {
                statusMessage = "State 1"
                dismiss()
            } label: {
                Text("Btn1")
            }

            Button {
                statusMessage = "State 2"
                dismiss()
            } label: {
                Text("Btn2")
            }
        }
    }
}

둘 다 State Variable을 toggle 한 것과 같은 결과를 볼 수 있다.

주의할 점은 sheet나 fullScreenCover를 직접 구현하는 게 아닌 따로 구현하고,
해당 View에서 호출할 수 있도록 해 줘야 한다는 점이다.
직접 구현하는 경우에 사용하면 sheet나 fullScreenCover 대신 애꿎은 상위 View가 사라지는 걸 볼 수 있다.

 

Popover


새로운 화면에 표시할 정보가 적은 경우, 화면이 큰 아이패드 등의 기기에서는
화면을 가득 채우게 되는 sheet나 fullScreenCover 방식이 비효율적일 수 있다.
이러한 경우 popover를 사용하면 조금 더 효율적인 방식으로 정보를 표시할 수 있다.

struct ContentView: View {
	@State var popState = false
	@State private var value = ""
	
	var body: some View {
		VStack {
			Text(value)
			
			Button("Present Sheet") {
				popState.toggle()
			}
		}
		.popover(isPresented: $popState) {
			Button("set1") {
				value = "1"
				popState.toggle()
			}
		}
	}
}

사용 방법도 sheet나 fullScreenCover와 크게 다르지 않다.

더보기

popover(isPresented:attachmentAnchor:arrowEdge:content:)

 

Apple Developer Documentation

 

developer.apple.com

생성자 파라미터는 다음과 같다.

  • isPresented
    표시 여부를 결정하는 State Variable을 전달한다.
  • attachmentAnchor
    frame이나 좌표를 전달해 Anchor가 표시될 위치를 조정할 수 있다.
    보통은 trigger View의 frame을 전달하거나 기본값을 사용한다.
  • arrowEdge
    macOS에서 popover의 화살표 방향을 결정한다.
    iOS는 무시한다.
  • content
    popover의 UI를 구성한다.

 

Interactive Dismiss

sheet 방식은 State Variable을 변경하거나,
기타 방법으로 dismiss를 호출하는 것 외에도 위에서 알래로 당겨 dismiss를 실행할 수 있다.

문제는 이런 제스처가 사용자의 입력 화면을 의도치 않게 날릴 수 있다는 점인데,
이 제스처를 끄는 것이 가능하다.

struct ContentView: View {
    @State var popState = false
    @State private var value = ""

    var body: some View {
        VStack {
            Text(value)

            Button("Present Sheet") {
                popState.toggle()
            }
        }
        .sheet(isPresented: $popState) {
            Button("set1") {
                value = "1"
                popState.toggle()
            }
            .interactiveDismissDisabled()
        }
    }
}

sheet에 속한 View에 'interactiveDismissDisabled' modifier를 추가한다.
sheet에 추가하는 것이 아닌 sheet에 속한 View에 추가하는 것을 명심하자.

기능 자체를 끄는 것은 간단하지만
해당 기능과 더불에 알림을 띄우거나 별도의 작업을 진행하는 것은 조금 더 복잡하다고 한다.

'학습 노트 > Swift UI (2022)' 카테고리의 다른 글

13. Label & Font  (0) 2022.09.28
12. Text  (0) 2022.09.28
08 ~09. Alert & Confirmation Dialog  (0) 2022.09.23
07. Overlay  (0) 2022.09.22
05 ~ 06. Scroll View & Form  (0) 2022.09.22