How to use
struct ContentView: View {
@State var presentTop: Bool = false
var body: some View {
VStack {
TPButton(title: "☝️", action: {
presentTop = true
})
}
.navigationBarTitleDisplayMode(.inline) // if it's needed
.toast(isPresented: $presentTop, edge: .top) { toastContent }
}
var toastContent: some View {
HStack {
Image(systemName: "checkmark.circle").font(.headline)
Text("Toast!").font(.headline)
}
.frame(maxHeight: .infinity)
.padding()
.background(RoundedRectangle(cornerRadius: 8.0).foregroundColor(.random)) // set color whatever you want
}
3 Steps
- Create ToastView
- Create ViewModifier
- Create ViewModifier
Step1: Create ToastView
struct Toast<Content:View>: View {
@Binding var isPresented: Bool
@State var edge: Edge
var content: Content
let action: (() -> Void)?
private var alignment: Alignment {
switch edge {
case .top: return .top
case .leading: return .leading
case .bottom: return .bottom
case .trailing: return .trailing
}
}
init(isPresented: Binding<Bool>, edge: Edge, action: (() -> Void)? = nil, @ViewBuilder content: () -> Content) {
_isPresented = isPresented
_edge = State(wrappedValue: edge)
self.action = action
self.content = content()
}
var body: some View {
ZStack(alignment: alignment) {
Spacer().frame(maxWidth: .infinity, maxHeight: .infinity)
content
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
.animation(.spring(), value: isPresented)
.transition(.move(edge: edge).combined(with: .opacity))
.onTapGesture {
withAnimation {
isPresented = false
action?()
}
}
}
}
Step2: Create ViewModifier
struct ToastModifier<ToastContent: View>: ViewModifier {
@Binding var isPresented: Bool
var toast: Toast<ToastContent>
init(isPresented: Binding<Bool>, edge: Edge, action: (() -> Void)? = nil, @ViewBuilder toastContent: () -> ToastContent) {
_isPresented = isPresented
toast = Toast(isPresented: isPresented, edge: edge, action: action, content: toastContent)
}
public func body(content: Content) -> some View {
ZStack {
content
if isPresented { toast }
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.clear.ignoresSafeArea(.all, edges: .all))
.animation(.spring(), value: isPresented)
}
}
Step3: View Extension
public extension View {
func toast<Content:View>(isPresented: Binding<Bool>, edge: Edge, action: (() -> Void)? = nil, @ViewBuilder content: () -> Content) -> some View {
self.modifier(ToastModifier(isPresented: isPresented, edge: edge, action: action, toastContent: content))
}
}
Top comments (1)
ToastModifier has animation problem. When
isPresented
becamefalse
and ToastView will be hidden, it'll be hidden without fade-out animation.Solution's here. 👇