1. toast message 4가지 종류로 정의
enum FancyToastStyle {
case error
case warning
case success
case info
}
extension FancyToastStyle {
var themeColor: Color {
switch self {
case .error: return Color.red
case .warning: return Color.orange
case .info: return Color.blue
case .success: return Color.green
}
}
var iconFileName: String {
switch self {
case .info: return "info.circle.fill"
case .warning: return "exclamationmark.triangle.fill"
case .success: return "checkmark.circle.fill"
case .error: return "xmark.circle.fill"
}
}
}
2. Toast message를 구성하는 데이터 형식 선언
struct FancyToast: Equatable {
var type: FancyToastStyle
var title: String
var message: String
var duration: Double = 3
}
3. Toast View 만들기
struct FancyToastView: View {
var type: FancyToastStyle
var title: String
var message: String
var onCancelTapped: (() -> Void)
var body: some View {
VStack(alignment: .leading) {
HStack(alignment: .top) {
Image(systemName: type.iconFileName)
.foregroundColor(type.themeColor)
VStack(alignment: .leading) {
Text(title)
.font(.system(size: 14, weight: .semibold))
Text(message)
.font(.system(size: 12))
.foregroundColor(Color.black.opacity(0.6))
}
Spacer(minLength: 10)
Button {
onCancelTapped()
} label: {
Image(systemName: "xmark")
.foregroundColor(Color.black)
}
}
.padding()
}
.background(Color.white)
.overlay(
Rectangle()
.fill(type.themeColor)
.frame(width: 6)
.clipped()
, alignment: .leading
)
.frame(minWidth: 0, maxWidth: .infinity)
.cornerRadius(8)
.shadow(color: Color.black.opacity(0.25), radius: 4, x: 0, y: 1)
.padding(.horizontal, 16)
}
}
4. Toast Modifier 만들고 View extension해서 사용할 수 있게 만들기
struct FancyToastModifier: ViewModifier {
@Binding var toast: FancyToast?
@State private var workItem: DispatchWorkItem?
func body(content: Content) -> some View {
content
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay(
ZStack {
mainToastView()
.offset(y: -30)
}.animation(.spring(), value: toast)
)
.onChange(of: toast) { value in
showToast()
}
}
@ViewBuilder func mainToastView() -> some View {
if let toast = toast {
VStack {
Spacer()
FancyToastView(
type: toast.type,
title: toast.title,
message: toast.message) {
dismissToast()
}
}
.transition(.move(edge: .bottom))
}
}
private func showToast() {
guard let toast = toast else { return }
UIImpactFeedbackGenerator(style: .light).impactOccurred()
if toast.duration > 0 {
workItem?.cancel()
let task = DispatchWorkItem {
dismissToast()
}
workItem = task
DispatchQueue.main.asyncAfter(deadline: .now() + toast.duration, execute: task)
}
}
private func dismissToast() {
withAnimation {
toast = nil
}
workItem?.cancel()
workItem = nil
}
}
extension View {
func toastView(toast: Binding<FancyToast?>) -> some View {
self.modifier(FancyToastModifier(toast: toast))
}
}
5. 기본 CententView 작성하기
struct ContentView: View {
var body: some View {
TabView {
VStack {
EmptyView()
}
.tabItem {
Text("기본검색")
}
VStack {
ToastBasicView()
}
.tabItem {
Text("토스트")
}
VStack {
EmptyView()
}
.tabItem {
Text("연습")
}
VStack {
EmptyView()
}
.padding()
.tabItem {
Text("커스텀검색")
}
}
}
}
6. Toast 누를 수 있는 버튼들 만들어서 기능 정상 작동 확인하기
아래에서 위로 토스트메시지가 올라옵니다.
struct ToastBasicView: View {
@State private var toast: FancyToast? = nil
var body: some View {
VStack {
Button {
toast = FancyToast(type: .info, title: "제목으로 안내해봅니다", message: "문제 없이 아주 잘 되었습니다 이것은 메시지입니다")
} label: {
Text("안내")
}
Button {
toast = FancyToast(type: .error, title: "제목으로 에러 발생시켜봅니다", message: "문제가 있을때 나오는 오류 메시지입니다")
} label: {
Text("오류")
}
Button {
toast = FancyToast(type: .warning, title: "제목으로 경고합니다", message: "주의해야할때 나오는 경고 메시지입니다")
} label: {
Text("경고")
}
Button {
toast = FancyToast(type: .success, title: "제목으로 성공 알립니다", message: "문제가 있을때 나오는 오류 메시지입니다")
} label: {
Text("성공")
}
}
.toastView(toast: $toast)
}
}