SwiftUI:可以从任何视图触发的全局覆盖
SwiftUI: Global Overlay That Can Be Triggered From Any View
我是 SwiftUI 框架的新手,我还没有完全理解它,所以请多多包涵。
有没有办法在绑定更改时从 "another view" 内部触发 "overlay view"?见下图:
我想这个 "overlay view" 会包含我所有的观点。我还不确定该怎么做——也许使用 ZIndex
。我也想当绑定更改时我需要某种回调,但我也不确定该怎么做。
这是我目前得到的:
ContentView
struct ContentView : View {
@State private var liked: Bool = false
var body: some View {
VStack {
LikeButton(liked: $liked)
}
}
}
点赞按钮
struct LikeButton : View {
@Binding var liked: Bool
var body: some View {
Button(action: { self.toggleLiked() }) {
Image(systemName: liked ? "heart" : "heart.fill")
}
}
private func toggleLiked() {
self.liked = !self.liked
// NEED SOME SORT OF TOAST CALLBACK HERE
}
}
我觉得我的 LikeButton
中需要某种回调,但我不确定这一切在 Swift 中是如何工作的。
如有任何帮助,我们将不胜感激。提前致谢!
使用 .presentation()
在点击按钮时显示提醒。
在LikeButton
中:
@Binding var liked: Bool
var body: some View {
Button(action: {self.liked = !self.liked}, label: {
Image(systemName: liked ? "heart.fill" : "heart")
}).presentation($liked) { () -> Alert in
Alert.init(title: Text("Thanks for liking!"))
}
}
您还可以使用 .presentation()
来呈现其他模态视图,例如 Popover
或 ActionSheet
。有关不同 .presentation()
选项的信息,请参阅 Apple 的 SwiftUI 文档中该页面的 here 和 "See Also" 部分。
编辑:使用自定义视图的示例 Popover
:
@State var liked = false
let popover = Popover(content: Text("Thanks for liking!").frame(width: 200, height: 100).background(Color.white), dismissHandler: {})
var body: some View {
Button(action: {self.liked = !self.liked}, label: {
Image(systemName: liked ? "heart.fill" : "heart")
}).presentation(liked ? popover : nil)
}
在 SwiftUI 中构建 "toast" 非常简单且有趣!
开始吧!
struct Toast<Presenting>: View where Presenting: View {
/// The binding that decides the appropriate drawing in the body.
@Binding var isShowing: Bool
/// The view that will be "presenting" this toast
let presenting: () -> Presenting
/// The text to show
let text: Text
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
self.presenting()
.blur(radius: self.isShowing ? 1 : 0)
VStack {
self.text
}
.frame(width: geometry.size.width / 2,
height: geometry.size.height / 5)
.background(Color.secondary.colorInvert())
.foregroundColor(Color.primary)
.cornerRadius(20)
.transition(.slide)
.opacity(self.isShowing ? 1 : 0)
}
}
}
}
正文说明:
GeometryReader
为我们提供了 superview 的首选大小,从而为我们的 Toast
. 提供了完美的大小
ZStack
将视图相互堆叠。
- 逻辑很简单:如果不应该看到吐司 (
isShowing == false
),那么我们渲染 presenting
视图。如果必须呈现吐司 (isShowing == true
),那么我们将 presenting
视图渲染得有点模糊 - 因为我们可以 - 接下来我们创建吐司。
- Toast 只是一个带有
Text
的 VStack
,具有自定义框架大小、一些设计花哨的功能(颜色和圆角半径)以及默认的 slide
过渡。
我在 View
上添加了这个方法,使 Toast
创建更容易:
extension View {
func toast(isShowing: Binding<Bool>, text: Text) -> some View {
Toast(isShowing: isShowing,
presenting: { self },
text: text)
}
}
以及有关如何使用它的小演示:
struct ContentView: View {
@State var showToast: Bool = false
var body: some View {
NavigationView {
List(0..<100) { item in
Text("\(item)")
}
.navigationBarTitle(Text("A List"), displayMode: .large)
.navigationBarItems(trailing: Button(action: {
withAnimation {
self.showToast.toggle()
}
}){
Text("Toggle toast")
})
}
.toast(isShowing: $showToast, text: Text("Hello toast!"))
}
}
我使用 NavigationView
来确保视图填满整个屏幕,因此 Toast
的大小和位置都正确。
withAnimation
块确保应用 Toast
转换。
外观:
借助 SwiftUI DSL 的强大功能,可以轻松扩展 Toast
。
Text
属性 可以很容易地成为一个 @ViewBuilder
闭包以适应最奢侈的布局。
将其添加到您的内容视图:
struct ContentView : View {
@State private var liked: Bool = false
var body: some View {
VStack {
LikeButton(liked: $liked)
}
// make it bigger by using "frame" or wrapping it in "NavigationView"
.toast(isShowing: $liked, text: Text("Hello toast!"))
}
}
如何在 2 秒后隐藏吐司(按要求):
在 toast VStack
中的 .transition(.slide)
之后附加此代码 VStack
。
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
self.isShowing = false
}
}
}
测试于 Xcode 11.1
我修改了上面 Matteo Pacini 的出色回答,合并了评论以使 Toast 在延迟后淡入和淡出。我还修改了 View 扩展,使其更通用一些,并接受类似于 .sheet 工作方式的尾随闭包。
ContentView.swift:
struct ContentView: View {
@State private var lightsOn: Bool = false
@State private var showToast: Bool = false
var body: some View {
VStack {
Button(action: {
if (!self.showToast) {
self.lightsOn.toggle()
withAnimation {
self.showToast = true
}
}
}){
Text("switch")
} //Button
.padding(.top)
Image(systemName: self.lightsOn ? "lightbulb" : "lightbulb.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.all)
.toast(isPresented: self.$showToast) {
HStack {
Text("Lights: \(self.lightsOn ? "ON" : "OFF")")
Image(systemName: self.lightsOn ? "lightbulb" : "lightbulb.fill")
} //HStack
} //toast
} //VStack
} //body
} //ContentView
查看+Toast.swift:
extension View {
func toast<Content>(isPresented: Binding<Bool>, content: @escaping () -> Content) -> some View where Content: View {
Toast(
isPresented: isPresented,
presenter: { self },
content: content
)
}
}
Toast.swift:
struct Toast<Presenting, Content>: View where Presenting: View, Content: View {
@Binding var isPresented: Bool
let presenter: () -> Presenting
let content: () -> Content
let delay: TimeInterval = 2
var body: some View {
if self.isPresented {
DispatchQueue.main.asyncAfter(deadline: .now() + self.delay) {
withAnimation {
self.isPresented = false
}
}
}
return GeometryReader { geometry in
ZStack(alignment: .bottom) {
self.presenter()
ZStack {
Capsule()
.fill(Color.gray)
self.content()
} //ZStack (inner)
.frame(width: geometry.size.width / 1.25, height: geometry.size.height / 10)
.opacity(self.isPresented ? 1 : 0)
} //ZStack (outer)
.padding(.bottom)
} //GeometryReader
} //body
} //Toast
有了它,您可以吐司文本或图像(或两者,如下所示)或任何其他视图。
我正在使用这个开源:https://github.com/huynguyencong/ToastSwiftUI。使用起来非常简单。
struct ContentView: View {
@State private var isShowingToast = false
var body: some View {
VStack(spacing: 20) {
Button("Show toast") {
self.isShowingToast = true
}
Spacer()
}
.padding()
// Just add a modifier to show a toast, with binding variable to control
.toast(isPresenting: $isShowingToast, dismissType: .after(3)) {
ToastView(message: "Hello world!", icon: .info)
}
}
}
App-wide 查看
如果您希望它是 app-wide,请在某处输入 app-wide!例如,您可以像这样将它添加到 MyProjectApp.swift
(或 sceneDelegate
中 UIKit/AppDelegate 项目)文件中:
请注意按钮和状态只是为了更多的解释,你可以考虑按照你喜欢的方式改变它们
@main
struct SwiftUIAppPlaygroundApp: App { // <- Note that where we are!
@State var showToast = false
var body: some Scene {
WindowGroup {
Button("App-Wide Button") { showToast.toggle() }
ZStack {
ContentView() // <- The app flow
if showToast {
MyCustomToastView().ignoresSafeArea(.all, edges: .all) // <- App-wide overlays
}
}
}
}
}
看到了吗?现在您可以在屏幕的任何位置添加任何类型的视图,不会阻止动画。只需将 @State 转换为某种 AppState,如 Observable
s 或 Environment
s 即可!你做到了!
这里是如何覆盖所有视图,包括 NavigationView!
创建一个 class 模型来存储您的观点!
class ParentView:ObservableObject {
@Published var view:AnyView = AnyView(EmptyView())
}
在父视图中创建模型并在视图层次结构中调用它
将此 class 传递给父视图的环境对象
struct Example: View {
@StateObject var parentView = ParentView()
var body: some View {
ZStack{
NavigationView{
ChildView()
.environmentObject(parentView)
.navigationTitle("dynamic parent view")
}
parentView.view
}
}
}
从现在开始,您可以通过
在子视图中调用父视图
@EnvironmentObject var parentView:ParentView
然后,例如在您的点击手势中,您可以更改父视图并显示一个涵盖所有内容的弹出窗口,包括您的导航视图
@StateObject var parentView = ParentView()
这是完整的解决方案副本,您可以在预览中使用它!
import SwiftUI
class ParentView:ObservableObject {
@Published var view:AnyView = AnyView(EmptyView())
}
struct example: View {
@StateObject var parentView = ParentView()
var body: some View {
ZStack{
NavigationView{
ChildView()
.environmentObject(parentView)
.navigationTitle("dynamic parent view")
}
parentView.view
}
}
}
struct ChildView: View {
@EnvironmentObject var parentView:ParentView
var body: some View {
ZStack{
Text("hello")
.onTapGesture {
parentView.view = AnyView(Color.red.opacity(0.4).ignoresSafeArea())
}
}
}
}
struct example_Previews: PreviewProvider {
static var previews: some View {
example()
}
}
您也可以像这样显着改善它...!
struct ParentViewModifire:ViewModifier {
@EnvironmentObject var parentView:ParentView
@Binding var presented:Bool
let anyView:AnyView
func body(content: Content) -> some View {
content
.onChange(of: presented, perform: { value in
if value {
parentView.view = anyView
}
})
}
}
extension View {
func overlayAll<Overlay>(_ overlay: Overlay, presented: Binding<Bool>) -> some View where Overlay : View {
self
.modifier(ParentViewModifire(presented: presented, anyView: AnyView(overlay)))
}
}
现在在您的子视图中,您可以在您的视图中调用此修饰符
struct ChildView: View {
@State var newItemPopUp:Bool = false
var body: some View {
ZStack{
Text("hello")
.overlayAll(newCardPopup, presented: $newItemPopUp)
}
}
}
Apple 当前不提供任何允许您制作类似于他们自己的警报弹出窗口的全局视图的 API。
事实上,这些视图实际上仍在使用 UIKit。
如果您想要自己的全局弹出窗口,您可以自行破解(请注意,这尚未经过测试,但非常相似的东西应该适用于全局的 toasts):
import SwiftUI
import Foundation
/// Global class that will manage toasts
class ToastPresenter: ObservableObject {
// This static property probably isn't even needed as you can inject via @EnvironmentObject
static let shared: ToastPresenter = ToastPresenter()
private init() {}
@Published private(set) var isPresented: Bool = false
private(set) var text: String?
private var timer: Timer?
/// Call this function to present toasts
func presentToast(text: String, duration: TimeInterval = 5) {
// reset the toast if one is currently being presented.
isPresented = false
self.text = nil
timer?.invalidate()
self.text = text
isPresented = true
timer = Timer(timeInterval: duration, repeats: false) { [weak self] _ in
self?.isPresented = false
}
}
}
/// The UI for a toast
struct Toast: View {
var text: String
var body: some View {
Text(text)
.padding()
.background(Capsule().fill(Color.gray))
.shadow(radius: 6)
.transition(AnyTransition.opacity.animation(.default))
}
}
extension View {
/// ViewModifier that will present a toast when its binding changes
@ViewBuilder func toast(presented: Binding<Bool>, text: String) -> some View {
ZStack {
self
if presented.wrappedValue {
Toast(text: text)
}
}
.ignoresSafeArea(.all, edges: .all)
}
}
/// The first view in your app's view hierarchy
struct RootView: View {
@StateObject var toastPresenter = ToastPresenter.shared
var body: some View {
MyAppMainView()
.toast(presented: $toastPresenter.isPresented, text: toastPresenter.text)
// Inject the toast presenter into the view hierarchy
.environmentObject(toastPresenter)
}
}
/// Some view later on in the app
struct SomeViewDeepInTheHierarchy: View {
@EnvironmentObject var toastPresenter: ToastPresenter
var body: some View {
Button {
toastPresenter.presentToast(text: "Hello World")
} label: {
Text("Show Toast")
}
}
}
我是 SwiftUI 框架的新手,我还没有完全理解它,所以请多多包涵。
有没有办法在绑定更改时从 "another view" 内部触发 "overlay view"?见下图:
我想这个 "overlay view" 会包含我所有的观点。我还不确定该怎么做——也许使用 ZIndex
。我也想当绑定更改时我需要某种回调,但我也不确定该怎么做。
这是我目前得到的:
ContentView
struct ContentView : View {
@State private var liked: Bool = false
var body: some View {
VStack {
LikeButton(liked: $liked)
}
}
}
点赞按钮
struct LikeButton : View {
@Binding var liked: Bool
var body: some View {
Button(action: { self.toggleLiked() }) {
Image(systemName: liked ? "heart" : "heart.fill")
}
}
private func toggleLiked() {
self.liked = !self.liked
// NEED SOME SORT OF TOAST CALLBACK HERE
}
}
我觉得我的 LikeButton
中需要某种回调,但我不确定这一切在 Swift 中是如何工作的。
如有任何帮助,我们将不胜感激。提前致谢!
使用 .presentation()
在点击按钮时显示提醒。
在LikeButton
中:
@Binding var liked: Bool
var body: some View {
Button(action: {self.liked = !self.liked}, label: {
Image(systemName: liked ? "heart.fill" : "heart")
}).presentation($liked) { () -> Alert in
Alert.init(title: Text("Thanks for liking!"))
}
}
您还可以使用 .presentation()
来呈现其他模态视图,例如 Popover
或 ActionSheet
。有关不同 .presentation()
选项的信息,请参阅 Apple 的 SwiftUI 文档中该页面的 here 和 "See Also" 部分。
编辑:使用自定义视图的示例 Popover
:
@State var liked = false
let popover = Popover(content: Text("Thanks for liking!").frame(width: 200, height: 100).background(Color.white), dismissHandler: {})
var body: some View {
Button(action: {self.liked = !self.liked}, label: {
Image(systemName: liked ? "heart.fill" : "heart")
}).presentation(liked ? popover : nil)
}
在 SwiftUI 中构建 "toast" 非常简单且有趣!
开始吧!
struct Toast<Presenting>: View where Presenting: View {
/// The binding that decides the appropriate drawing in the body.
@Binding var isShowing: Bool
/// The view that will be "presenting" this toast
let presenting: () -> Presenting
/// The text to show
let text: Text
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
self.presenting()
.blur(radius: self.isShowing ? 1 : 0)
VStack {
self.text
}
.frame(width: geometry.size.width / 2,
height: geometry.size.height / 5)
.background(Color.secondary.colorInvert())
.foregroundColor(Color.primary)
.cornerRadius(20)
.transition(.slide)
.opacity(self.isShowing ? 1 : 0)
}
}
}
}
正文说明:
GeometryReader
为我们提供了 superview 的首选大小,从而为我们的Toast
. 提供了完美的大小
ZStack
将视图相互堆叠。- 逻辑很简单:如果不应该看到吐司 (
isShowing == false
),那么我们渲染presenting
视图。如果必须呈现吐司 (isShowing == true
),那么我们将presenting
视图渲染得有点模糊 - 因为我们可以 - 接下来我们创建吐司。 - Toast 只是一个带有
Text
的VStack
,具有自定义框架大小、一些设计花哨的功能(颜色和圆角半径)以及默认的slide
过渡。
我在 View
上添加了这个方法,使 Toast
创建更容易:
extension View {
func toast(isShowing: Binding<Bool>, text: Text) -> some View {
Toast(isShowing: isShowing,
presenting: { self },
text: text)
}
}
以及有关如何使用它的小演示:
struct ContentView: View {
@State var showToast: Bool = false
var body: some View {
NavigationView {
List(0..<100) { item in
Text("\(item)")
}
.navigationBarTitle(Text("A List"), displayMode: .large)
.navigationBarItems(trailing: Button(action: {
withAnimation {
self.showToast.toggle()
}
}){
Text("Toggle toast")
})
}
.toast(isShowing: $showToast, text: Text("Hello toast!"))
}
}
我使用 NavigationView
来确保视图填满整个屏幕,因此 Toast
的大小和位置都正确。
withAnimation
块确保应用 Toast
转换。
外观:
借助 SwiftUI DSL 的强大功能,可以轻松扩展 Toast
。
Text
属性 可以很容易地成为一个 @ViewBuilder
闭包以适应最奢侈的布局。
将其添加到您的内容视图:
struct ContentView : View {
@State private var liked: Bool = false
var body: some View {
VStack {
LikeButton(liked: $liked)
}
// make it bigger by using "frame" or wrapping it in "NavigationView"
.toast(isShowing: $liked, text: Text("Hello toast!"))
}
}
如何在 2 秒后隐藏吐司(按要求):
在 toast VStack
中的 .transition(.slide)
之后附加此代码 VStack
。
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
self.isShowing = false
}
}
}
测试于 Xcode 11.1
我修改了上面 Matteo Pacini 的出色回答,合并了评论以使 Toast 在延迟后淡入和淡出。我还修改了 View 扩展,使其更通用一些,并接受类似于 .sheet 工作方式的尾随闭包。
ContentView.swift:
struct ContentView: View {
@State private var lightsOn: Bool = false
@State private var showToast: Bool = false
var body: some View {
VStack {
Button(action: {
if (!self.showToast) {
self.lightsOn.toggle()
withAnimation {
self.showToast = true
}
}
}){
Text("switch")
} //Button
.padding(.top)
Image(systemName: self.lightsOn ? "lightbulb" : "lightbulb.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.all)
.toast(isPresented: self.$showToast) {
HStack {
Text("Lights: \(self.lightsOn ? "ON" : "OFF")")
Image(systemName: self.lightsOn ? "lightbulb" : "lightbulb.fill")
} //HStack
} //toast
} //VStack
} //body
} //ContentView
查看+Toast.swift:
extension View {
func toast<Content>(isPresented: Binding<Bool>, content: @escaping () -> Content) -> some View where Content: View {
Toast(
isPresented: isPresented,
presenter: { self },
content: content
)
}
}
Toast.swift:
struct Toast<Presenting, Content>: View where Presenting: View, Content: View {
@Binding var isPresented: Bool
let presenter: () -> Presenting
let content: () -> Content
let delay: TimeInterval = 2
var body: some View {
if self.isPresented {
DispatchQueue.main.asyncAfter(deadline: .now() + self.delay) {
withAnimation {
self.isPresented = false
}
}
}
return GeometryReader { geometry in
ZStack(alignment: .bottom) {
self.presenter()
ZStack {
Capsule()
.fill(Color.gray)
self.content()
} //ZStack (inner)
.frame(width: geometry.size.width / 1.25, height: geometry.size.height / 10)
.opacity(self.isPresented ? 1 : 0)
} //ZStack (outer)
.padding(.bottom)
} //GeometryReader
} //body
} //Toast
有了它,您可以吐司文本或图像(或两者,如下所示)或任何其他视图。
我正在使用这个开源:https://github.com/huynguyencong/ToastSwiftUI。使用起来非常简单。
struct ContentView: View {
@State private var isShowingToast = false
var body: some View {
VStack(spacing: 20) {
Button("Show toast") {
self.isShowingToast = true
}
Spacer()
}
.padding()
// Just add a modifier to show a toast, with binding variable to control
.toast(isPresenting: $isShowingToast, dismissType: .after(3)) {
ToastView(message: "Hello world!", icon: .info)
}
}
}
App-wide 查看
如果您希望它是 app-wide,请在某处输入 app-wide!例如,您可以像这样将它添加到 MyProjectApp.swift
(或 sceneDelegate
中 UIKit/AppDelegate 项目)文件中:
请注意按钮和状态只是为了更多的解释,你可以考虑按照你喜欢的方式改变它们
@main
struct SwiftUIAppPlaygroundApp: App { // <- Note that where we are!
@State var showToast = false
var body: some Scene {
WindowGroup {
Button("App-Wide Button") { showToast.toggle() }
ZStack {
ContentView() // <- The app flow
if showToast {
MyCustomToastView().ignoresSafeArea(.all, edges: .all) // <- App-wide overlays
}
}
}
}
}
看到了吗?现在您可以在屏幕的任何位置添加任何类型的视图,不会阻止动画。只需将 @State 转换为某种 AppState,如 Observable
s 或 Environment
s 即可!你做到了!
这里是如何覆盖所有视图,包括 NavigationView!
创建一个 class 模型来存储您的观点!
class ParentView:ObservableObject {
@Published var view:AnyView = AnyView(EmptyView())
}
在父视图中创建模型并在视图层次结构中调用它 将此 class 传递给父视图的环境对象
struct Example: View {
@StateObject var parentView = ParentView()
var body: some View {
ZStack{
NavigationView{
ChildView()
.environmentObject(parentView)
.navigationTitle("dynamic parent view")
}
parentView.view
}
}
}
从现在开始,您可以通过
在子视图中调用父视图@EnvironmentObject var parentView:ParentView
然后,例如在您的点击手势中,您可以更改父视图并显示一个涵盖所有内容的弹出窗口,包括您的导航视图
@StateObject var parentView = ParentView()
这是完整的解决方案副本,您可以在预览中使用它!
import SwiftUI
class ParentView:ObservableObject {
@Published var view:AnyView = AnyView(EmptyView())
}
struct example: View {
@StateObject var parentView = ParentView()
var body: some View {
ZStack{
NavigationView{
ChildView()
.environmentObject(parentView)
.navigationTitle("dynamic parent view")
}
parentView.view
}
}
}
struct ChildView: View {
@EnvironmentObject var parentView:ParentView
var body: some View {
ZStack{
Text("hello")
.onTapGesture {
parentView.view = AnyView(Color.red.opacity(0.4).ignoresSafeArea())
}
}
}
}
struct example_Previews: PreviewProvider {
static var previews: some View {
example()
}
}
您也可以像这样显着改善它...!
struct ParentViewModifire:ViewModifier {
@EnvironmentObject var parentView:ParentView
@Binding var presented:Bool
let anyView:AnyView
func body(content: Content) -> some View {
content
.onChange(of: presented, perform: { value in
if value {
parentView.view = anyView
}
})
}
}
extension View {
func overlayAll<Overlay>(_ overlay: Overlay, presented: Binding<Bool>) -> some View where Overlay : View {
self
.modifier(ParentViewModifire(presented: presented, anyView: AnyView(overlay)))
}
}
现在在您的子视图中,您可以在您的视图中调用此修饰符
struct ChildView: View {
@State var newItemPopUp:Bool = false
var body: some View {
ZStack{
Text("hello")
.overlayAll(newCardPopup, presented: $newItemPopUp)
}
}
}
Apple 当前不提供任何允许您制作类似于他们自己的警报弹出窗口的全局视图的 API。
事实上,这些视图实际上仍在使用 UIKit。
如果您想要自己的全局弹出窗口,您可以自行破解(请注意,这尚未经过测试,但非常相似的东西应该适用于全局的 toasts):
import SwiftUI
import Foundation
/// Global class that will manage toasts
class ToastPresenter: ObservableObject {
// This static property probably isn't even needed as you can inject via @EnvironmentObject
static let shared: ToastPresenter = ToastPresenter()
private init() {}
@Published private(set) var isPresented: Bool = false
private(set) var text: String?
private var timer: Timer?
/// Call this function to present toasts
func presentToast(text: String, duration: TimeInterval = 5) {
// reset the toast if one is currently being presented.
isPresented = false
self.text = nil
timer?.invalidate()
self.text = text
isPresented = true
timer = Timer(timeInterval: duration, repeats: false) { [weak self] _ in
self?.isPresented = false
}
}
}
/// The UI for a toast
struct Toast: View {
var text: String
var body: some View {
Text(text)
.padding()
.background(Capsule().fill(Color.gray))
.shadow(radius: 6)
.transition(AnyTransition.opacity.animation(.default))
}
}
extension View {
/// ViewModifier that will present a toast when its binding changes
@ViewBuilder func toast(presented: Binding<Bool>, text: String) -> some View {
ZStack {
self
if presented.wrappedValue {
Toast(text: text)
}
}
.ignoresSafeArea(.all, edges: .all)
}
}
/// The first view in your app's view hierarchy
struct RootView: View {
@StateObject var toastPresenter = ToastPresenter.shared
var body: some View {
MyAppMainView()
.toast(presented: $toastPresenter.isPresented, text: toastPresenter.text)
// Inject the toast presenter into the view hierarchy
.environmentObject(toastPresenter)
}
}
/// Some view later on in the app
struct SomeViewDeepInTheHierarchy: View {
@EnvironmentObject var toastPresenter: ToastPresenter
var body: some View {
Button {
toastPresenter.presentToast(text: "Hello World")
} label: {
Text("Show Toast")
}
}
}