Is there a way to change views based off of Environment Variables in SwiftUI? - swift

I want to be able to change a view in SwiftUI with the tap of a button. I have buttons setup to toggle the environmental variables as follows
struct SettingsButton: View {
#EnvironmentObject var settings: UserSettings
var body: some View {
VStack {
Button(action: { self.settings.settingsView.toggle() }) {
Image(systemName: "gear")
.font(Font.system(size: 25))
.frame(width: 25, height: 25)
.foregroundColor(.primary)
}
}
.offset(x: 180, y: -372)}
}
I've also declared the Observable object here
import Foundation
import GoogleSignIn
class UserSettings: ObservableObject {
#Published var studentID = ""
#Published var givenName = ""
#Published var settingsView = false
#Published var profileView = false
#Published var isLogged = GIDSignIn.sharedInstance()?.currentUser
}
And finally I have a ViewBuilder setup in the view that is loaded on start to listen for a change in the variable and to switch views accordingly, however when the app is loaded and the button is tapped the app freezes and remains unresponsive.
struct Login: View {
#EnvironmentObject var settings: UserSettings
#ViewBuilder var body : some View {
if settings.isLogged != nil {
MainView()
}
else {
LoginPage()
}
if settings.settingsView {
SettingsView()
}
}
}
I would like to know if there is any known way to attempt this without the use of .sheet or Navigation Links any help with be very much appreciated!

Without seeing your MainView(), LoginPage() and SettingsView() I think you should be doing something like this in your Login() view:
I added VStack around your views:
struct Login: View {
#EnvironmentObject var settings: UserSettings
#ViewBuilder var body: some View {
VStack {
if settings.isLogged != nil {
MainView()
} else {
LoginPage()
}
if settings.settingsView {
SettingsView()
}
}
}
}
Also ensure that you have the following in your SceneDelegate since your UserSettings() is defined as an EnvironmentObject:
// Create the SwiftUI view that provides the window contents.
let contentView = Login()
.environmentObject(UserSettings())

Related

Show new view and completely destroy old view when clicking on Text

So I have been wanting to do this for some time, but I can't figure out how to approach this, so I'm reaching out to see if someone might be able to help me.
So let's say that I have the following code, which when the app loads, loads the "MainView":
struct MapGlider: App {
#ObservedObject var mainViewModel = MainViewModel()
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(mainViewModel)
}
}
}
This loads the map as soon as the app is opened, which is great! All works great there.
Now I will be switching that out to show the OnboardingView() when the app loads such as:
struct MapGlider: App {
#ObservedObject var mainViewModel = MainViewModel()
var body: some Scene {
WindowGroup {
OnboardingView()
}
}
}
Now, I have a OnboardingView that shows a ZStack with some options, as show in this code below:
struct OnboardingView: View {
#State private var showGetStartedSheet = false
#ObservedObject var mainViewModel = MainViewModel()
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
ZStack(alignment: .top) {
VStack {
LazyVGrid(columns: [GridItem(), GridItem(), GridItem(alignment: .topTrailing)], content: {
Spacer()
Image("onboarding-logo")
.border(.red)
NavigationLink(destination: MainView().environmentObject(mainViewModel), label: {
Text("Skip")
})
})
.border(.red)
}
}
.border(.blue)
}
} else {
// Fallback on earlier versions
}
}
}
Which outputs the following:
What I'm trying to achieve:
When someone clicks on the "Skip" text, to kill the OnboardingView and show the MainView().
The closest I got is setting a NavigationLink, but that had a back button and doesn't work so well, I want to be able to go to the MainView and not be able to go back to OnboardingView.
All help will be appreciated!
You could use a container view that conditionally displays the onboarding or main views, depending on the state of a variable (stored at the parent level). That variable can be passed down via a Binding:
Simplified example that should be easily applicable to your code:
class AppState: ObservableObject {
#Published var showOnboarding = true
}
#main
struct CustomCardViewApp: App {
#StateObject var appState = AppState()
var body: some Scene {
WindowGroup {
MainScreenContainer(showOnboarding: $appState.showOnboarding)
}
}
}
struct MainScreenContainer: View {
#Binding var showOnboarding: Bool
var body: some View {
if showOnboarding {
OnboardingView(showOnboarding: $showOnboarding)
} else {
MainView()
}
}
}
struct OnboardingView: View {
#Binding var showOnboarding: Bool
var body: some View {
Text("Onboarding")
Button("Skip") {
showOnboarding = false
}
}
}
struct MainView: View {
var body: some View {
Text("Main")
}
}

SwiftUI: UserDefaults Binding

I have a settings view that has a button which toggles a binding that's stored with UserDefaults.
struct Settings: View {
#ObservedObject var settingsVM = SetttingsViewModel()
var body: some View {
if settingsVM.settingActivated {
Text("Setting activated")
} else {
Text("Setting deactivated")
}
Button("Activate") {
settingsVM.settingActivated.toggle()
}
}
}
SettingsViewModel
class SetttingsViewModel: ObservableObject {
#Published var settingActivated: Bool = UserDefaults.standard.bool(forKey: "settingActivated") {
didSet {
UserDefaults.standard.set(self.settingActivated, forKey: "settingActivated")
}
}
}
The text("Setting activated/ Setting deactivated")in the Settings view update instantly when i press the button but the text in ContentView doesn't change unless i restart the app & i have no idea why.
struct ContentView: View {
#ObservedObject var settingsVM = SetttingsViewModel()
#State private var showsettings = false
var body: some View {
if settingsVM.settingActivated {
Text("Setting Activated")
.padding(.top)
} else {
Text("Setting Deactivated")
.padding(.top)
}
Button("Show Settings") {
showsettings.toggle()
}
.sheet(isPresented: $showsettings) {
Settings()
}
.frame(width: 300, height: 300)
}
}
This is for a macOS 10.15 app so i can't use #AppStorage
Right now, you don't have any code in you view model to react to a change in UserDefaults. Meaning, if UserDefaults gets a new value set, it won't know about it. And, since you're using a different instance of SettingsViewModel in your two different views, they can easily become out-of-sync.
The easiest change would be to pass the same instance of SettingsViewModel to Settings:
struct Settings: View {
#ObservedObject var settingsVM: SettingsViewModel //<-- Here
var body: some View {
if settingsVM.settingActivated {
Text("Setting activated")
} else {
Text("Setting deactivated")
}
Button("Activate") {
settingsVM.settingActivated.toggle()
}
}
}
struct ContentView: View {
#ObservedObject var settingsVM = SetttingsViewModel()
#State private var showsettings = false
var body: some View {
if settingsVM.settingActivated {
Text("Setting Activated")
.padding(.top)
} else {
Text("Setting Deactivated")
.padding(.top)
}
Button("Show Settings") {
showsettings.toggle()
}
.sheet(isPresented: $showsettings) {
Settings(settingsVM: settingsVM) //<-- Here
}
.frame(width: 300, height: 300)
}
}
Another option would be to use a custom property wrapper (like AppStorage, but available to earlier targets): https://xavierlowmiller.github.io/blog/2020/09/04/iOS-13-AppStorage
Also, #vadian's comment is important -- if you had access to it, you'd want to use #StateObject. But, since you don't, it's important to store your ObservableObject at the top level so it doesn't get recreated.

Use Swift ObservableObject to change view label when UserSettings change

I’ve created a small sample project in Swift Playgrounds to debug an issue I’ve encountered. This sample project contains the a primary ContentView with a single Text field and a button that opens Settings in a modal view.
When I open Settings and change the a setting via a picker, I would like to see the corresponding Text label change in my ContentView. In the current project, I’m using the #ObservableObject Type Alias to track the change, and I see that the setting changes correctly, but the view is not updated. If I restart the preview in Playgrounds, the view is updated with the changed setting. I would expect the Text label to change in real-time.
The code is as follows:
ContentView.swift
import SwiftUI
struct ContentView: View {
#ObservedObject var userSettings = UserSettings()
#State var isModal: Bool = false
var body: some View {
VStack {
Text("Setting: " + userSettings.pickerSetting)
.fontWeight(.semibold)
.font(.title)
Button(action: {
self.isModal = true
}) {
Image(systemName: "gear")
.font(.title)
}
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(40)
.sheet(isPresented: $isModal, content: {
UserSettingsView()
})
.environmentObject(userSettings)
}
}
}
UserSettings.swift
import Foundation
class UserSettings: ObservableObject {
#Published var pickerSetting: String {
didSet {
UserDefaults.standard.set(pickerSetting, forKey: "pickerSetting")
}
}
public var pickerSettings = ["Setting 1", "Setting 2", "Setting 3"]
init() {
self.pickerSetting = UserDefaults.standard.object(forKey: "pickerSetting") as? String ?? "Setting 1"
}
}
UserSettingsView.swift
import SwiftUI
struct UserSettingsView: View {
#ObservedObject var userSettings = UserSettings()
var body: some View {
NavigationView {
Form {
Section(header: Text("")) {
Picker(selection: $userSettings.pickerSetting, label: Text("Picker Setting")) {
ForEach(userSettings.pickerSettings, id: \.self) { setting in
Text(setting)
}
}
}
}
.navigationBarTitle("Settings")
}
}
}
This happening because you have created two instances of UserSettings. One each in ContentView and UserSettingsView.
If you want to keep using .environmentObject(userSettings) the you need to use #EnvironmentObject var userSettings: UserSettings in UserSettingsView.
Otherwise you can drop the .environmentObject and use an #ObservedObject in UserSettingsView.

SwiftUI macOS Commands (menu bar) and View

Hi I am starting to learn SwiftUI and macOS development. I am using the SwiftUI life cycle. How do I call a function from the focused window from the menu bar.
Besides Apple documentation, I found this reference and am able to create menu items using Commands but I have no idea how to call a function from my view.
For example:
Suppose this is my App struct:
import SwiftUI
#main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}.commands {
CommandMenu("First menu") {
Button("Action!") {
// How do I call the views action function?
}
}
}
}
and this is my View:
struct ContentView: View {
public func action() {
print("It works")
}
var body: some View {
Text("Example")
}
}
I just typed the example code sorry if there are any typos but I hope you can get the idea.
Because Views in SwiftUI are transient, you can't hold a reference to a specific instance of ContentView to call a function on it. What you can do, though, is change part of your state that gets passed down to the content view.
For example:
#main
struct ExampleApp: App {
#StateObject var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView(appState: appState)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}.commands {
CommandMenu("First menu") {
Button("Action!") {
appState.textToDisplay = "\(Date())"
}
}
}
}
}
class AppState : ObservableObject {
#Published var textToDisplay = "(not clicked yet)"
}
struct ContentView: View {
#ObservedObject var appState : AppState
var body: some View {
Text(appState.textToDisplay)
}
}
Note that the .commands modifier goes on WindowGroup { }
In this example, AppState is an ObservableObject that holds some state of the app. It's passed through to ContentView using a parameter. You could also pass it via an Environment Object (https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views)
When the menu item is clicked, it sets textToDisplay which is a #Published property on AppState. ContentView will get updated any time a #Published property of AppState gets updated.
This is the general idea of the pattern you'd use. If you have a use case that isn't covered by this pattern, let me know in the comments.
Updates, based on your comments:
import SwiftUI
import Combine
#main
struct ExampleApp: App {
#StateObject var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView(appState: appState)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}.commands {
CommandMenu("First menu") {
Button("Action!") {
appState.textToDisplay = "\(Date())"
}
Button("Change background color") {
appState.contentBackgroundColor = Color.green
}
Button("Toggle view") {
appState.viewShown.toggle()
}
Button("CustomCopy") {
appState.customCopy.send()
}
}
}
}
}
class AppState : ObservableObject {
#Published var textToDisplay = "(not clicked yet)"
#Published var contentBackgroundColor = Color.clear
#Published var viewShown = true
var customCopy = PassthroughSubject<Void,Never>()
}
class ViewModel : ObservableObject {
#Published var text = "The text I have here"
var cancellable : AnyCancellable?
func connect(withAppState appState: AppState) {
cancellable = appState.customCopy.sink(receiveValue: { _ in
print("Do custom copy based on my state: \(self.text) or call a function")
})
}
}
struct ContentView: View {
#ObservedObject var appState : AppState
#State var text = "The text I have here"
#StateObject private var viewModel = ViewModel()
var body: some View {
VStack {
Text(appState.textToDisplay)
.background(appState.contentBackgroundColor)
if appState.viewShown {
Text("Shown?")
}
}
.onReceive(appState.$textToDisplay) { (newText) in
print("Got new text: \(newText)")
}
.onAppear {
viewModel.connect(withAppState: appState)
}
}
}
In my updates, you can see that I've addressed the question of the background color, showing hiding a view, and even getting a notification (via onReceive) when one of the #Published properties changes.
You can also see how I use a custom publisher (customCopy) to pass along an action to ContentView's ViewModel

SwiftUI: Global Overlay That Can Be Triggered From Any View

I'm quite new to the SwiftUI framework and I haven't wrapped my head around all of it yet so please bear with me.
Is there a way to trigger an "overlay view" from inside "another view" when its binding changes? See illustration below:
I figure this "overlay view" would wrap all my views. I'm not sure how to do this yet - maybe using ZIndex. I also guess I'd need some sort of callback when the binding changes, but I'm also not sure how to do that either.
This is what I've got so far:
ContentView
struct ContentView : View {
#State private var liked: Bool = false
var body: some View {
VStack {
LikeButton(liked: $liked)
}
}
}
LikeButton
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
}
}
I feel like I need some sort of callback inside my LikeButton, but I'm not sure how this all works in Swift.
Any help with this would be appreciated. Thanks in advance!
It's quite easy - and entertaining - to build a "toast" in SwiftUI!
Let's do it!
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)
}
}
}
}
Explanation of the body:
GeometryReader gives us the preferred size of the superview , thus allowing the perfect sizing for our Toast.
ZStack stacks views on top of each other.
The logic is trivial: if the toast is not supposed to be seen (isShowing == false), then we render the presenting view. If the toast has to be presented (isShowing == true), then we render the presenting view with a little bit of blur - because we can - and we create our toast next.
The toast is just a VStack with a Text, with custom frame sizing, some design bells and whistles (colors and corner radius), and a default slide transition.
I added this method on View to make the Toast creation easier:
extension View {
func toast(isShowing: Binding<Bool>, text: Text) -> some View {
Toast(isShowing: isShowing,
presenting: { self },
text: text)
}
}
And a little demo on how to use it:
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!"))
}
}
I used a NavigationView to make sure the view fills the entire screen, so the Toast is sized and positioned correctly.
The withAnimation block ensures the Toast transition is applied.
How it looks:
It's easy to extend the Toast with the power of SwiftUI DSL.
The Text property can easily become a #ViewBuilder closure to accomodate the most extravagant of the layouts.
To add it to your content view:
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!"))
}
}
How to hide the toast afte 2 seconds (as requested):
Append this code after .transition(.slide) in the toast VStack.
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
self.isShowing = false
}
}
}
Tested on Xcode 11.1
I modified Matteo Pacini's great answer, above, incorporating comments to have the Toast fade in and fade out after a delay. I also modified the View extension to be a bit more generic, and to accept a trailing closure similar to the way .sheet works.
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
View+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
With this you could toast Text, or an Image (or both, as shown below), or any other View.
here is the how to overlay on all of your views including NavigationView!
create a class model to store your views!
class ParentView:ObservableObject {
#Published var view:AnyView = AnyView(EmptyView())
}
create the model in your parrent view and call it in your view hierarchy
pass this class to your environment object of your parent view
struct Example: View {
#StateObject var parentView = ParentView()
var body: some View {
ZStack{
NavigationView{
ChildView()
.environmentObject(parentView)
.navigationTitle("dynamic parent view")
}
parentView.view
}
}
}
from now on you can call parentview in your child view by
#EnvironmentObject var parentView:ParentView
then for example in your tap gesture, you can change the parent view and show a pop up that covers everything including your navigationviews
#StateObject var parentView = ParentView()
here is the full solution copy and play with it in your preview!
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()
}
}
also you can improve this dramatically like this...!
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)))
}
}
now in your child view you can call this modifier on your view
struct ChildView: View {
#State var newItemPopUp:Bool = false
var body: some View {
ZStack{
Text("hello")
.overlayAll(newCardPopup, presented: $newItemPopUp)
}
}
}
App-wide View
If you want it to be app-wide, put in somewhere app-wide! For example, you can add it to the MyProjectApp.swift (or in sceneDelegate for UIKit/AppDelegate projects) file like this:
Note that the button and the State are just for more explanation and you may consider changing them in the way you like
#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
}
}
}
}
}
See? now you can add any sort of view on anywhere of the screen, without blocking animations. Just convert that #State to some sort of AppState like Observables or Environments and boom! 💥 you did it!
Note that it is a demo, you should use an environment variable or smt to be able for changing it from outside of this view's body
Apple does not currently provide any APIs that allow you to make global views similar to their own alert pop-ups.
In fact these views are actually still using UIKit under the hood.
If you want your own global pop-ups you can sort of hack your own (note this isn't tested, but something very similar should work for global presentation of 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")
}
}
}
Use .presentation() to show an alert when the button is tapped.
In 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!"))
}
}
You can also use .presentation() to present other Modal views, like a Popover or ActionSheet. See here and the "See Also" section on that page in Apple's SwiftUI documentation for info on the different .presentation() options.
Edit: Example of what you want with a custom view using 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)
}
I am using this open source: https://github.com/huynguyencong/ToastSwiftUI . It is very simple to use.
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)
}
}
}