職場のプロジェクトで目にする機会があったので備忘録として、、(情報漏洩はよろしくないのでUIとか実装はしっかりアレンジしています)
Contents 非表示
実装 (全部コピペでOK)
Modelとロジック側
import Foundation
import SwiftUI
struct FancyToast: Equatable {
var type: FancyToastStyle
var title: String
var message: String
var duration: Double = 10
}
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"
}
}
}
import Foundation
import SwiftUI
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()
// Durationが0以下の場合は、手動で閉じる
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))
}
}
import SwiftUI
class HomeViewModel: ObservableObject {
@Published var toast: FancyToast?
@Published var toastTitleArray = ["Error", "Warning", "Success", "Info"]
@Published var toastColorArray: [Color] = [.red, .yellow, .green, .blue]
@Published var toastArray = [
FancyToast(type: .error, title: "Toast Error", message: "Toast message"),
FancyToast(type: .warning, title: "Toast Warning", message: "Toast message"),
FancyToast(type: .success, title: "Toast Success", message: "Toast message"),
FancyToast(type: .info, title: "Toast Info", message: "Toast message")
]
}
View側
import SwiftUI
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)
}
}
struct FancyToastView_Previews: PreviewProvider {
static var previews: some View {
VStack {
FancyToastView(
type: .error,
title: "Error",
message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ") {}
FancyToastView(
type: .warning,
title: "Warning",
message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ") {}
FancyToastView(
type: .success,
title: "Success",
message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ") {}
FancyToastView(
type: .info,
title: "Info",
message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ") {}
}
}
}
import SwiftUI
struct HomeView: View {
@ObservedObject private var homeViewModel = HomeViewModel()
var body: some View {
VStack(spacing: 20) {
ForEach(0..<homeViewModel.toastArray.count, id: \.self) { index in
Button {
homeViewModel.toast = homeViewModel.toastArray[index]
} label: {
Text("\(homeViewModel.toastTitleArray[index])")
.font(.largeTitle)
.foregroundColor(.white)
}
.padding()
.frame(width: 300, height: 80)
.background(homeViewModel.toastColorArray[index])
.cornerRadius(16)
}
}
.toastView(toast: $homeViewModel.toast)
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}