sheet, transition, animation을 사용해서 popover 뷰를 만들어보겠습니다.
sheet
sheet는 흔히 밑에서 위로 올라오는 뷰를 의미하며 isPresented 의 결과가 true로 바뀌었을 경우 sheet가 나타납니다
파라미터
isPresented
: Boolean 타입의 값과 바인딩을 하여 해당 값이 true일 때 클로저 내부에 해당하는 콘텐츠를 나타냅니다.
onDismiss
: sheet를 없앨 때 실행하는 클로저입니다. sheet가 없어진 이후 동작할 코드를 작성하면 됩니다.
content
: sheet에 포함할 콘텐츠를 넣습니다.
struct ContentView: View {
@State var showNewScreen: Bool = false
var body: some View {
ZStack {
Color.orange
.ignoresSafeArea()
VStack {
Button("BUTTON") {
showNewScreen = true
}
Spacer()
}
// METHOD 1 - SHEET
.sheet(isPresented: $showNewScreen, content: {
NewScreen()
})
}
}
}
struct NewScreen: View {
@Environment(\.dismiss) var dismiss
var body: some View {
ZStack(alignment: .topLeading) {
Color.purple
.ignoresSafeArea()
Button(action: {
dismiss()
}, label: {
Image(systemName: "xmark")
.foregroundStyle(.white)
.font(.largeTitle)
.padding(20)
})
}
}
}
transition
transition은 뷰와 전환을 연관시킵니다
뷰가 나타나거나 사라질 때 animation을 사용하여 transition 이 적용됩니다.
struct ContentView: View {
@State var showNewScreen: Bool = false
var body: some View {
ZStack {
Color.orange
.ignoresSafeArea()
VStack {
Button("BUTTON") {
withAnimation(.spring) {
showNewScreen = true
}
}
Spacer()
}
// METHOD 2 - TRANSITION
ZStack {
if showNewScreen {
NewScreen(showNewScreen: $showNewScreen)
.padding(.top, 100)
.transition(.move(edge: .bottom))
}
}
.zIndex(2.0)
}
}
}
struct NewScreen: View {
@Environment(\.dismiss) var dismiss
@Binding var showNewScreen: Bool
var body: some View {
ZStack(alignment: .topLeading) {
Color.purple
.ignoresSafeArea()
Button(action: {
withAnimation(.spring) {
showNewScreen.toggle()
}
}, label: {
Image(systemName: "xmark")
.foregroundStyle(.white)
.font(.largeTitle)
.padding(20)
})
}
}
}
알고가면 좋은점 zIndex
위에서 zIndex 를 사용하였습니다. 이때 zIndex란 ZStack에서의 뷰에 나타내는 우선 순위를 의미합니다.
ZStack {
if showNewScreen {
NewScreen(showNewScreen: $showNewScreen)
.padding(.top, 100)
.transition(.move(edge: .bottom))
}
}
.zIndex(2.0)
만약 뷰의 앞뒤 순서를 제어하려면 zIndex(_:)를 사용합니다
파라미터
value
: 앞에서 뒤로 보내는 우선순위를 나타내는 값으로 기본값은 0입니다.
이 예제에는 두 개의 겹치는 회전된 직사각형이 있습니다. 가장 앞쪽은 더 큰 인덱스 값으로 표현된다. (즉 큰값이 사용자한테 먼저 보여짐)
VStack {
Rectangle()
.fill(Color.yellow)
.frame(width: 100, height: 100, alignment: .center)
.zIndex(1) // Top layer.
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100, alignment: .center)
.rotationEffect(.degrees(45))
// Here a zIndex of 0 is the default making
// this the bottom layer.
}
다음식으로 말이죠.
offset
지정된 수평 및 수직 거리로 이 뷰를 이동할 수 있게 됩니다
파라미터
x
: x축으로 뷰의 위치를 어느정도 이동시킬 것인지를 결정합니다
y
: y축으로 뷰의 위치를 어느정도 이동시킬 것인지를 결정합니다
반환값
설정한 x축과 y축을 기준으로 이동한 view를 반환합니다
Offset(x:y:)를 사용하여 표시된 내용을 x 및 y 매개 변수에 지정된 위치로 이동할 수 있습니다. 아래의 예제에서 이 보기에 의해 그려진 회색 테두리는 텍스트의 원래 위치를 둘러싸고 있습니다. (x축이 증가하면 밑으로, x축이 감소하면 위로 이동하며 y도 동일합니다)
Text("Offset by passing horizontal & vertical distance")
.border(Color.green)
.offset(x: 20, y: 50)
.border(Color.gray)
import SwiftUI
struct ContentView: View {
@State var showNewScreen: Bool = false
let window = UIApplication.shared.connectedScenes.first as! UIWindowScene
var body: some View {
ZStack {
Color.orange
.ignoresSafeArea()
VStack {
Button("BUTTON") {
withAnimation(.spring) {
showNewScreen = true
}
}
Spacer()
}
// METHOD 3 - ANIMATION OFFSET
NewScreen(showNewScreen: $showNewScreen)
.padding(.top, 100)
.offset(y: showNewScreen ? 0 : window.screen.bounds.height)
}
}
}
struct NewScreen: View {
@Environment(\.dismiss) var dismiss
@Binding var showNewScreen: Bool
var body: some View {
ZStack(alignment: .topLeading) {
Color.purple
.ignoresSafeArea()
Button(action: {
withAnimation(.spring) {
showNewScreen.toggle()
}
}, label: {
Image(systemName: "xmark")
.foregroundStyle(.white)
.font(.largeTitle)
.padding(20)
})
}
}
}
그렇다면 왜 다르게 사용하느냐?
위에서 보았듯이 결국 3개 다 동일하게 동작합니다. 하지만 커스텀화하기 위해서는 sheet 사용하지 않고 transition 또는 offset을 사용해야할 수 있습니다.
왜냐면 sheet는 아래서 위로 즉 transition(.move(edge: .bottom)), offset(y: )와 같이 아래서 위로 올라가는 기능밖에 하지 못하지만 나머지 2개의 경우는 다르게 설정할 수 있기 때문입니다.
transition을 다르게 구현해보기
만약 transition(.move(edge: .top))으로 동작하도록 구현한다면 아래처럼 동작할 것입니다.
struct ContentView: View {
@State var showNewScreen: Bool = false
let window = UIApplication.shared.connectedScenes.first as! UIWindowScene
var body: some View {
ZStack {
Color.orange
.ignoresSafeArea()
VStack {
Button("BUTTON") {
withAnimation(.spring) {
showNewScreen = true
}
}
Spacer()
}
// METHOD 2 - TRANSITION
ZStack {
if showNewScreen {
NewScreen(showNewScreen: $showNewScreen)
.padding(.top, 100)
.transition(.move(edge: .top))
}
}
.zIndex(2.0)
}
}
}
struct NewScreen: View {
@Environment(\.dismiss) var dismiss
@Binding var showNewScreen: Bool
var body: some View {
ZStack(alignment: .topLeading) {
Color.purple
.ignoresSafeArea()
Button(action: {
withAnimation(.spring) {
showNewScreen.toggle()
}
}, label: {
Image(systemName: "xmark")
.foregroundStyle(.white)
.font(.largeTitle)
.padding(20)
})
}
}
}
offset을 다르게 해보기
만약 위에 기능을 똑같이 offset으로 구현한다면 아래와 같습니다
import SwiftUI
struct ContentView: View {
@State var showNewScreen: Bool = false
let window = UIApplication.shared.connectedScenes.first as! UIWindowScene
var body: some View {
ZStack {
Color.orange
.ignoresSafeArea()
VStack {
Button("BUTTON") {
withAnimation(.spring) {
showNewScreen = true
}
}
Spacer()
}
// METHOD 3 - ANIMATION OFFSET
NewScreen(showNewScreen: $showNewScreen)
.padding(.top, 100)
.offset(y: showNewScreen ? 0 : -window.screen.bounds.height)
}
}
}
struct NewScreen: View {
@Environment(\.dismiss) var dismiss
@Binding var showNewScreen: Bool
var body: some View {
ZStack(alignment: .topLeading) {
Color.purple
.ignoresSafeArea()
Button(action: {
withAnimation(.spring) {
showNewScreen.toggle()
}
}, label: {
Image(systemName: "xmark")
.foregroundStyle(.white)
.font(.largeTitle)
.padding(20)
})
}
}
}
총 코드
import SwiftUI
struct ContentView: View {
@State var showNewScreen: Bool = false
let window = UIApplication.shared.connectedScenes.first as! UIWindowScene
var body: some View {
ZStack {
Color.orange
.ignoresSafeArea()
VStack {
Button("BUTTON") {
// showNewScreen = true
withAnimation(.spring) {
showNewScreen = true
}
}
Spacer()
}
// METHOD 1 - SHEET
// .sheet(isPresented: $showNewScreen, content: {
// NewScreen()
// })
// METHOD 2 - TRANSITION
// ZStack {
// if showNewScreen {
// NewScreen(showNewScreen: $showNewScreen)
// .padding(.top, 100)
// .transition(.move(edge: .top))
// }
// }
// .zIndex(2.0)
// METHOD 3 - ANIMATION OFFSET
NewScreen(showNewScreen: $showNewScreen)
.padding(.top, 100)
.offset(y: showNewScreen ? 0 : -window.screen.bounds.height)
}
}
}
struct NewScreen: View {
@Environment(\.dismiss) var dismiss
@Binding var showNewScreen: Bool
var body: some View {
ZStack(alignment: .topLeading) {
Color.purple
.ignoresSafeArea()
Button(action: {
// dismiss()
withAnimation(.spring) {
showNewScreen.toggle()
}
}, label: {
Image(systemName: "xmark")
.foregroundStyle(.white)
.font(.largeTitle)
.padding(20)
})
}
}
}
#Preview {
ContentView()
}
'SwiftUI' 카테고리의 다른 글
@FocusState 로 키보드 다루기 (0) | 2024.04.06 |
---|---|
swipeActions (0) | 2024.04.06 |
더 적은 데이터로 더 많은 작업하기 (0) | 2024.04.06 |
WWDC 2022 BackgroundTask (0) | 2024.04.02 |
Could not first-time schedule app refresh: Error Domain=BGTaskSchedulerErrorDomain Code=3 (0) | 2024.04.02 |