diff --git a/Projects/Shared/DesignSystem/Example/Sources/Component/ToastDetailView.swift b/Projects/Shared/DesignSystem/Example/Sources/Component/ToastDetailView.swift new file mode 100644 index 0000000..ed9200b --- /dev/null +++ b/Projects/Shared/DesignSystem/Example/Sources/Component/ToastDetailView.swift @@ -0,0 +1,30 @@ +// +// ToastDetailView.swift +// DesignSystemExample +// +// Created by devMinseok on 8/16/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import DesignSystem + +struct ToastDetailView: View { + @State var toast: DefaultToast? + + var body: some View { + VStack { + Button { + toast = DefaultToast(message: "Thist is test message for toast", image: Image(systemName: "left")) + } label: { + Text("showToast") + } + } + .toastDestination(toast: $toast) + } +} + +#Preview { + ToastDetailView() +} diff --git a/Projects/Shared/DesignSystem/Example/Sources/ContentView.swift b/Projects/Shared/DesignSystem/Example/Sources/ContentView.swift index a41de69..6f44c93 100644 --- a/Projects/Shared/DesignSystem/Example/Sources/ContentView.swift +++ b/Projects/Shared/DesignSystem/Example/Sources/ContentView.swift @@ -60,6 +60,12 @@ struct ContentView: View { Text("BottomSheet") } + NavigationLink { + ToastDetailView() + } label: { + Text("Toast") + } + NavigationLink { } label: { @@ -69,7 +75,7 @@ struct ContentView: View { NavigationLink { } label: { - Text("Toast") + Text("TextField") } } } diff --git a/Projects/Shared/DesignSystem/Sources/Component/BottomSheet/BottomSheet.swift b/Projects/Shared/DesignSystem/Sources/Component/BottomSheet/BottomSheetViewModifier.swift similarity index 73% rename from Projects/Shared/DesignSystem/Sources/Component/BottomSheet/BottomSheet.swift rename to Projects/Shared/DesignSystem/Sources/Component/BottomSheet/BottomSheetViewModifier.swift index 7ed52a5..950bbed 100644 --- a/Projects/Shared/DesignSystem/Sources/Component/BottomSheet/BottomSheet.swift +++ b/Projects/Shared/DesignSystem/Sources/Component/BottomSheet/BottomSheetViewModifier.swift @@ -8,23 +8,24 @@ import SwiftUI -extension View { - public func bottomSheet< - Item: Identifiable, - Content: View - >( - item: Binding, - @ViewBuilder content: @escaping (Item) -> Content - ) -> some View { +struct BottomSheetViewModifier< + Item: Identifiable, + BottomSheetContent: View +>: ViewModifier { + @Binding var item: Item? + let bottomSheetContent: (Item) -> BottomSheetContent + + func body(content: Content) -> some View { ZStack(alignment: .bottom) { - self + content + .frame(maxWidth: .infinity, maxHeight: .infinity) .zIndex(1) - if let wrappedItem = item.wrappedValue { + if let item { Global.Color.black.opacity(Global.Opacity._50d) .ignoresSafeArea() .onTapGesture { - item.wrappedValue = nil + self.item = nil } .transition( .opacity.animation(.easeInOut) @@ -44,14 +45,12 @@ extension View { DragGesture(minimumDistance: 20) .onEnded { value in if value.translation.height > 100 { - withAnimation { - item.wrappedValue = nil - } + self.item = nil } } ) - content(wrappedItem) + bottomSheetContent(item) .frame(maxWidth: .infinity) .fixedSize(horizontal: false, vertical: true) .background(Global.Color.white) @@ -59,9 +58,21 @@ extension View { .frame(maxWidth: .infinity) .frame(maxHeight: UIScreen.main.bounds.height * 0.9, alignment: .bottom) .transition(.move(edge: .bottom)) - .animation(.spring(duration: 0.4)) .zIndex(3) } } + .animation(.spring(duration: 0.4), value: self.item == nil) + } +} + +extension View { + public func bottomSheet< + Item: Identifiable, + Content: View + >( + item: Binding, + @ViewBuilder content: @escaping (Item) -> Content + ) -> some View { + return self.modifier(BottomSheetViewModifier(item: item, bottomSheetContent: content)) } } diff --git a/Projects/Shared/DesignSystem/Sources/Component/Toast/Toast.swift b/Projects/Shared/DesignSystem/Sources/Component/Toast/Toast.swift new file mode 100644 index 0000000..b6babae --- /dev/null +++ b/Projects/Shared/DesignSystem/Sources/Component/Toast/Toast.swift @@ -0,0 +1,30 @@ +// +// Toast.swift +// DesignSystem +// +// Created by devMinseok on 8/16/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +public protocol Toast: Equatable { + var message: String { get } + var image: Image? { get } + var hideAutomatically: Bool { get } +} + +public struct DefaultToast: Toast { + public let message: String + public let image: Image? + public let hideAutomatically: Bool + + public init( + message: String, + image: Image + ) { + self.message = message + self.image = image + hideAutomatically = true + } +} diff --git a/Projects/Shared/DesignSystem/Sources/Component/Toast/ToastViewModifier.swift b/Projects/Shared/DesignSystem/Sources/Component/Toast/ToastViewModifier.swift new file mode 100644 index 0000000..9ed6ba7 --- /dev/null +++ b/Projects/Shared/DesignSystem/Sources/Component/Toast/ToastViewModifier.swift @@ -0,0 +1,71 @@ +// +// ToastView.swift +// DesignSystem +// +// Created by devMinseok on 8/16/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +struct ToastViewModifier: ViewModifier { + @Binding var toast: T? + + init(toast: Binding) { + _toast = toast + } + + func body(content: Content) -> some View { + ZStack(alignment: .bottom ) { + content + .frame(maxWidth: .infinity, maxHeight: .infinity) + .zIndex(1) + + if let toast { + HStack(alignment: .center, spacing: 8) { + if let image = toast.image { + image + } + Text(toast.message) + .foregroundStyle(Global.Color.white) + .font(Typography.subBodyR) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + } + .padding(16) + .background( + RoundedRectangle(cornerRadius: 16) + .fill(Alias.Color.Background.inverse) + .opacity(Global.Opacity._90d) + ) + .padding(.horizontal, Global.Dimension._20f) + .transition( + .move(edge: .bottom) + .combined(with: .opacity.animation(.easeInOut)) + ) + .onAppear { + if toast.hideAutomatically { + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + if self.toast != nil { + self.toast = nil + } + } + } + } + .onTapGesture { + self.toast = nil + } + .zIndex(2) + } + } + .animation(.spring(duration: 0.3), value: self.toast) + } +} + +extension View { + public func toastDestination( + toast: Binding + ) -> some View { + self.modifier(ToastViewModifier(toast: toast)) + } +}