How do I implement a NavigationLink into a Button(action: { }) - swiftui

I have a problem: I donĀ“t know how to implement a NavigationLink into a Button -> I want to create a Button named "Order now" for a shopping app. Depending on the payment that was chosen by the user I want to show different views after the user clicks on the button.
Button(action: {
if self.cart.orders.count > 0 {
switch self.paymentOption {
case 0:
default:
break
}
} else {
self.confirmationAlert.toggle()
}
}) {
Text("Order now")
}
Thanks for your help in advance!

Related

How do we prevent the .onTapGesture on other views from firing when SwiftUI menu is visible on the screen?

Here is my code:
/* triggers a full screen view on parent to be able to trap clicks anywhere on the screen and prevent other views .ontap from firing.
This works when the use taps outside, when the user makes a change but does not work when the user selects the already selected menu item. In that case the menu does disappear but I can't toggle the parent's trapping view because .disappear for the menu or the picker doesn't actually fire.
*/
#Binding var menuShowing: Bool
....
Menu {
Picker("Topic", selection: $selectedTopic) {
Text("Unassigned")
.tag(nil as Topic?)
Divider()
ForEach(topics, id:\.id) { topic in
Text(topic.name!)
.tag(topic as Topic?)
}
}
.onDisappear{
print("Picker Disappeared") // THIS DOES NOT FIRE IF THE USER SELECTS THE ALREADY SELECTED ITEM
}
}
label: {
Image(systemName: "circle.grid.3x3.circle")
.font(.system(size: 26,weight: .ultraLight))
.foregroundColor(learningSet.topic?.foregroundColor ?? .primary)
.frame(width:30, height: 30)
}
.contentShape(Rectangle())
.onDisappear{ // THIS DOES NOT FIRE IF THE USER SELECTS THE ALREADY SELECTED ITEM
print("Menu Disappeared")
}
.onTapGesture {
vm.haptic()
menuShowing.toggle()
}
.onChange(of: selectedTopic, perform: { _ in
menuShowing = false
DispatchQueue.main.async {
if learningSet.topic != selectedTopic{
learningSet.lastModifiedOn = .now
learningSet.topic?.lastModifiedOn = .now
learningSet.topic = selectedTopic
if selectedTopic == nil{
vm.unassignedExpanded = true
} else{
selectedTopic?.lastModifiedOn = .now
selectedTopic?.expanded = true
}
_ = vm.save("Changed Topic for set \(learningSet.name ?? "..") to \(String(describing: selectedTopic?.name))")
withAnimation(.easeInOut(duration: 0.15)){
forceRefresh.toggle()
}
}
}
})
This correctly shows the menu and if I select an item, the .onchange fires like it should.
Problem is if the user clicks on some other view on the screen while the is active, it fires that views .onTappedGesture.
I tried different combinations of highPriorityGesture and SimultaneousGesture to no avail.
I also tried the approach of adding a full screen view In between the view with the menu and the underlying views and added an onTap to it. That approach works fine with one issue: If the user click on the currency selected item, the .onChange for the picker doesn't fire, the menu disappears but its .onDisappear doesn't fire. So there is no way to know the menu is no longer visible, and hide my full screen view.
Any help would be greatly appreciated.
Thanks.

SwiftUI Toggle changing state when entering background

I have a view with a list and inside the list there is a toggle that is binded with a boolean in the viewmodel, if I turn on the toggle the boolean is true and viceversa, the issue here is that if I turn on the toggle, and then enter background when I reopen the app, the toggle appears turned off even when the boolean is true, then I go back to the previous screen and when I return to the screen with the toggle it appears turned on, is there a way to avoid this issue? here is the List code below:
List {
Section(header: Text("Flags")) {
ForEach(viewModel.flags.indices, id: \.self) { index in
Toggle(isOn: $viewModel.flags[index].isActive) {
Text(viewModel.flags[index].name.rawValue.camelCaseToCapitalized())
}
}
}
You can try to use the onAppear modifier to update the state of the toggle when the view appears.
List {
Section(header: Text("Flags")) {
ForEach(viewModel.flags.indices, id: \.self) { index in
Toggle(isOn: $viewModel.flags[index].isActive) {
Text(viewModel.flags[index].name.rawValue.camelCaseToCapitalized())
}
.onAppear {
viewModel.flags[index].isActive = true
}
}
}

Is it possible to add swipe actions dynamically in SwiftUI?

The question is quite simple. I receive entities from API that contains property actions and I need to display them in a list with actions as swipe actions accordingly.
Problem is that I didn't find the way how to iterate actions in swipeActions modificator.
I tried something like this:
List {
ForEach(entities) { entity in
CustomCell(entity: entity)
.swipeActions(edge: .trailing) {
for action in entity.actions {
Button(action: {}) {
Label(action.title)
}
}
}
}
}
and it does not work.
Is it even possible to solve?
Assuming action type is Identifiable (if not then confirm it), it can be done with ForEach, because swipeActions expects views, so it can be like
List {
ForEach(entities) { entity in
CustomCell(entity: entity)
.swipeActions(edge: .trailing) {
ForEach(entity.actions) { action in // << here !!
Button(action: {}) {
Label(action.title)
}
}
}
}
}

Toolbar menu is closed when updates are made to UI in SwiftUI

I've noticed a problem with the toolbar menu closing when updates are made to the UI. I made a test project to verify this is the case, and the code is shown below. Here are the steps to reproduce the issue.
Open the menu
Wait 5s before an update is made to the UI (in this case the name)
The menu will automatically close
I found this problem is directly caused by the button action code: selection = String(describing: View2.self). Here are the test cases I tried to find this out.
If I comment the selection code, then the problem doesn't exist.
To test that it isn't because it's linked to the NavigationLink, I commented the NavigationLink and uncommented the selection code, but the problem still exists.
If I replace the selection code with a simple print statement, then the problem doesn't exist.
I'm using Xcode 12.5.1. Tested on iOS 14.
How can I fix this problem?
struct ContentView: View {
#State var name = "John"
#State var selection: String?
var body: some View {
NavigationView {
ZStack {
NavigationLink(destination: View2(), tag: String(describing: View2.self), selection: $selection){}
Text("Hello \(name)")
.padding()
}
.toolbar {
Menu {
Button {
selection = String(describing: View2.self)
} label: {
Text("Edit")
}
Button {
selection = String(describing: View2.self)
} label: {
Text("Help")
}
} label: {
Text("Menu")
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
name = "Hunter"
}
}
}
}
}

SwiftUI .swipeactions and Core Data

Currently using Xcode 13 and IOS 15.
I am using CoreData to display a list of upcoming trips. On each trip I have 2 swipe actions, one to delete the trip, and the second one is to edit the trip. When the Edit button is selected I then trigger the showingEditTripScreen to true so that the EditTripScreen sheet is shown. I am passing the trip into this sheet to be edited. The problem is that no matter what Trip from the ForEach row is selected, it is always the first trip data that is being sent to the EditTripScreen. Am I doing this properly, or is their another solution.
Thanks
ForEach(tripVM.trips, id: \.id) { trip in
TripCardView(trip: trip)
.listRowSeparator(.hidden)
//.padding(.horizontal)
.swipeActions(allowsFullSwipe: false) {
// Edit Trip
Button {
showingEditTripScreen = true
} label: {
Label("Edit", systemImage: "pencil.circle.fill")
}
.tint(.green)
// Delete Trip
Button {
tripVM.deleteTrip(trip: trip)
tripVM.getAllTrips()
} label: {
Label("Delete", systemImage: "trash.circle.fill")
}
.tint(.red)
}
.sheet(isPresented: $showingEditTripScreen, onDismiss: {
}, content: {
EditTripScreen(trip: trip)
})
}
You're adding sheet for each cell of your table. So when you set showingEditTripScreen variable to true, all sheets for visible views gets triggered, and only one of them will be shown, maybe the first one or just a random one.
Instead you need to store selected trip and use sheet(item:onDismiss:content:), which will pass you unwrapped item to your content. And this sheet should be single one for your list, not need to add it to each item.
Also onDismiss is an optional parameter, you don't need to pass it if you're not using it. editingTrip will be set to nil when you dismiss it automatically.
#State
var editingTrip: Trip?
var body: some View {
ForEach(tripVM.trips, id: \.id) { trip in
TripCardView(trip: trip)
.listRowSeparator(.hidden)
//.padding(.horizontal)
.swipeActions(allowsFullSwipe: false) {
// Edit Trip
Button {
editingTrip = trip
} label: {
Label("Edit", systemImage: "pencil.circle.fill")
}
.tint(.green)
// Delete Trip
Button {
tripVM.deleteTrip(trip: trip)
tripVM.getAllTrips()
} label: {
Label("Delete", systemImage: "trash.circle.fill")
}
.tint(.red)
}
}
.sheet(item: $editingTrip, content: { editingTrip in
EditTripScreen(trip: editingTrip)
})
}