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:)
fullScreenCover(isPresented:onDismiss:content:)
생성자 파라미터는 아래와 같다
- 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:)
생성자 파라미터는 다음과 같다.
- 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에 추가하는 것을 명심하자.
interactiveDismissDisabled(_:)
기능 자체를 끄는 것은 간단하지만
해당 기능과 더불에 알림을 띄우거나 별도의 작업을 진행하는 것은 조금 더 복잡하다고 한다.
'학습 노트 > 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 |