문제
SwiftUI에서 fullScreen 전환은 애니메이션 효과가 한정적이었다. 기존 UIKit에서 사용하던 화면 전환 방식과 동일한 사용자 경험을 구현해야 했기에 fullScreen을 사용하지 않고 화면을 전환할 방법을 찾아야 했다.
접근
ZStack을 활용해 뷰 위에 뷰를 띄우자!
이것이 이번 이슈의 핵심 해결 접근이었다.
(본 글의 코드는 화면 전환 중심으로 작성되었기에 UI는 간단하게 구성되어 있다. 그래서 UI 코드는... 깨끗하지 않다.)
1. ZStack을 선택한 이유
왜 ZStack을 쓰고자 했냐 - 하면, ZStack에 대한 정의가 필요하다. 공식문서를 읽어보자.
A view that overlays its subviews, aligning them in both axes. (...) The ZStack assigns each successive subview a higher z-axis value than the one before it, meaning later subviews appear “on top” of earlier ones.
즉, ZStack은 순서대로 뷰를 위에 쌓아 올리며, 가장 마지막에 선언된 뷰가 최상단에 위치한다.
2. ZStack 활용 조건:
그래야 각 뷰의 z축 순서를 지정할 수 있다.
구현
팝업 View
해당 뷰의 구현 내용 및 설명은 주석으로 작성했다.
import SwiftUI
struct CustomPopUpView: View {
// MARK: - properites
// 해당 프로퍼티로 팝업을 띄울지 말지를 부모뷰의 state 값으로 전달 받을 예정
@Binding var showPopUp: Bool
let title: String // 공지 이름
let message: String // 공지 내용
let btn1: String // 왼쪽 버튼 text
let btn2: String // 오른쪽 버튼 text
// MARK: - body
var body: some View {
ZStack {
/// Background - 팝업을 띄울 때 팝업 외 배경을 어둡게 하고자 했다
Rectangle()
.background(.black)
.opacity(0.2)
VStack(spacing: 12) {
Text(title)
.frame(height: 30)
.fontWeight(.bold)
.padding(.top, 12)
Divider()
Text(message)
.multilineTextAlignment(.center)
.padding(.vertical, 12)
HStack(spacing: 8) {
Button(action: {
print("확인 누름")
}, label: {
Text(btn1)
.fontWeight(.semibold)
.foregroundStyle(.white)
.padding(.vertical, 8)
.frame(width: 100)
})
.background(
/// 버튼 테두리
RoundedRectangle(cornerRadius: 8)
.foregroundStyle(.brown)
)
Button(action: {
print("취소 누름")
showPopUp = false
}, label: {
Text(btn2)
.foregroundStyle(.gray)
.padding(.vertical, 8)
.frame(width: 100)
})
.background(
/// 버튼 테두리
RoundedRectangle(cornerRadius: 8)
.stroke(.gray, lineWidth: 1)
)
}
.frame(height: 44)
.padding(.bottom, 12)
}
.background(.white)
.roundedCorner(16, corners: .allCorners)
.padding(.horizontal, 20)
}
// ZStack을 화면에 꽉 채우도록 해서 fullScreen처럼 보이게 함
.ignoresSafeArea(.all)
}
}
#Preview {
CustomPopUpView(
showPopUp: .constant(false),
title: "공지",
message: "정말로 삭제를 하시겠습니까?",
btn1: "확인",
btn2: "취소"
)
}
팝업을 띄울 View
해당 뷰의 구현 내용 및 설명은 주석으로 작성했다.
import SwiftUI
struct ContentView: View {
// 팝업 띄움 여부 결정
@State private var showPopUp: Bool = false
var body: some View {
ZStack {
VStack(spacing: 16) {
Text("test popup animation")
.font(.title)
Button("팝업 띄우기") {
print("버튼 누름")
// 팝업 띄우기 버튼을 누르면 state 변수에 바뀐 값을 전달해준다
// withAnimation으로 애니메이션 효과를 커스텀해준다.
// withAnimation에 종류와 타이머 지정까지 가능하니 필요 시 찾아보기!
withAnimation {
showPopUp = true
}
}
.foregroundStyle(.white)
.padding(.horizontal, 20)
.frame(height: 50)
.background(.gray)
}
// showPopUp 값이 true로 바뀌면 해당 팝업뷰를 띄워준다. (ZStack 활용!)
if showPopUp {
showPopUpView(
title: "안내",
message: "정말로 삭제를 하시겠습니까? \n삭제 시 데이터를 복구하실 수 없습니다.",
btn1: "확인",
btn2: "취소"
)
}
}
}
}
extension ContentView {
// showPopUP값에 따라 띄워줄 뷰를 편의상 extension에 함수로 view를 구현해두었다.
func showPopUpView(
title: String,
message: String,
btn1: String,
btn2: String
) -> some View {
CustomPopUpView(
showPopUp: $showPopUp,
title: title,
message: message,
btn1: btn1,
btn2: btn2
)
}
}
#Preview {
ContentView()
}
RoundedCorner는 타 블로그들에서 쉽게 참고할 수 있는 코드를 참고해서 작성했다
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
extension View {
func roundedCorner(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape(RoundedCorner(radius: radius, corners: corners) )
}
}
Reference
https://developer.apple.com/documentation/swiftui/zstack
ZStack | Apple Developer Documentation
A view that overlays its subviews, aligning them in both axes.
developer.apple.com
'Dev > 구현' 카테고리의 다른 글
[SwiftUI] Infinite Carousel 구현하기 2 (feat.Timer) (0) | 2024.09.11 |
---|---|
[SwiftUI] Infinite Carousel 구현하기 1 (feat. Timer) (0) | 2024.09.05 |
[SwiftUI] pagerView 만들기 (iOS 버전대응) (2) | 2024.08.14 |
[SwiftUI] 스유로 섹션 접었다폈다 구현하기 (0) | 2024.06.21 |
[SwiftUI] TabView page indicator 커스텀하기 (1) | 2024.04.25 |