SiwftUI nested buttons, child tap triggers parent animation - swiftui

I have been looking around but couldn't find the solution so lets hope someone can help me.
I have a ScrollView with a LazyVStack in it. Then I have a list of 'cells'.
Simplified it looks something like
Button(action: { ... }) {
HStack {
Text("Title")
Button(action: { ... }) {
Text("Sub action")
}
.buttonStyle(FancyButtonStyle())
}
}
.buttonStyle(CellBackgroundStyle())
The correct actions are triggered when tapped. But the problem is that when I tap the 'sub action' the 'CellBackgroundStyle' is triggered.
I did consider making the main button as the background but that did not work. Also putting them below each other would not work since I want a 'full cell tap animation'.
So does anyone here can explain to me how we can nest buttons in SwiftUI where the child does not trigger the parent animation?
Thanks in advance =]

Related

SwiftUI Mac: Adding buttonStyle Makes Button Click Propagate to Parent

I am trying to create a situation in a SwiftUI Mac app where when I click a Button inside a parent view, only the Button's action is trigger—not any of the tap gestures attached to its parent.
Here is a simple example that reproduces the problem:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 30){
Button("Button"){
print("button clicked")
}
.padding(5)
.background(Color.blue)
.buttonStyle(PlainButtonStyle())
}
.frame(width: 500, height: 500)
.background(Color.gray)
.padding(100)
.gesture(TapGesture(count: 2).onEnded {
//Double click (open message in popup)
print("double click")
})
.simultaneousGesture(TapGesture().onEnded {
if NSEvent.modifierFlags.contains(.command){
print("command click")
}else{
print("single click")
}
})
}
}
Clicking the button triggers both button clicked and single click.
If you comment out the buttonStyle...
//.buttonStyle(PlainButtonStyle())
It works how I want it to. Only button clicked is fired.
It doesn't seem to matter which button style I use, the behavior persists. I really need a custom button style on the child button in my situation, so how do I get around this?
If you replace your .simultaneousGesture with a regular .gesture it works for me – and still recognizes the outer single and double taps.

How do I disable a SwiftUI NavigationView list item row on load?

I have an array of objects (employees) that I am displaying in a navigationview. Each object has a boolean property called "Active". If the employee is active I want the navigationlink for that list item to work as it normally would but if the employee is not active then I want that list item to be disabled so that the navigationlink or any swipe actions do not work. This is my code:
NavigationView {
List {
CustomSearchBar(searchText: $searchText, searchCategory: $searchCategory)
ForEach(Employees) { person in
ZStack {
NavigationLink(destination: DisplayDetails().environmentObject(person)) {
ListItem().environmentObject(person)
}
}
.swipeActions(edge: .leading, content: {
Button {
Employees.toggleArchiveFlag(for: person.id)
} label: {
Label("Archive", systemImage: !person.archived ? "square.and.arrow.down" : "trash")
}
.tint(!person.archived ? .blue : .red)
})
.disabled(!person.active)
}
}
.navigationTitle("Current Employees")
.padding(.horizontal,-15) // remove the padding that the list adds to the screen
}
What ends up happing is that when the view initially loads everything is enabled regardless of each employee's active status. But if I click any of the navigationlinks to load the "DisplayDetails" detailed view and then return back to the main navigationview OR if I click on any of the searchbar toggles or use the searchbar to filter my list of people then the view updates and the correct people are disabled.
It is almost as if the statement ".disabled(!person.active)" is being called too late. If that is the case then where should I be calling it? I have tried moving that statement in the following places:
The closing bracket of the Zstack. But this does nothing
Right below the "ListItem().environmentObject(person)" statement but this still shows the same behavior as mentioned earlier and when the navigationlink is eventually disabled then the swipeactions are still enabled.
Any help at all would be appreciated!
Figured out that the issue was with the logic that set the person.active boolean value not with the presentation of the navigation view items. Thanks.

SwiftUI Buttons in a ScrollView is not tappable sometimes

I have a horizontal ScrollView on top of a MapView.
The ScorllView is a collection of Buttons. It is weird that the buttons in the ScrollView are sometime tapable and sometimes not. First tap always works but after that I have to scroll a bit, tap around different areas in the button, make some secret prayers and then it works!
I tried disabling/removing all other components in the view, but still unable to figure out the root cause.
Has anyone experience this ?
I stuck with a same issue with horizontal ScrollView on top and List. While debugging I added empty .onTapGesture to ScrollView and it somehow fix my issue.
VStack(spacing: 0) {
ScrollView(.horizontal) {
HStack {
Button("one") {}
Button("two") {}
Button("three") {}
}
}
.onTapGesture { // <---- fix
}
List {
}
}
I also faced the same issue for Horizontal Scroll view in Swiftui dark theme "CameraTimerItem" buttons not clickable (Problem with dark theme only). Then I put a onTapGesture without any action. It's starts to work normally. I think it's a error of SwiftUI.
VStack (alignment:.center){
ScrollView(.horizontal, showsIndicators: false) {
HStack{
ForEach(timeSlots,id: \.self) { item in
CameraTimerItem(cellTitle: item)
}
}
.frame(width: AppUtils.width, alignment: .center)
}
.onTapGesture {
// <---- This is the solution
}
}
To anyone else having this issue, instead of adding an empty .onTapGesture view modifier, check that any HStacks in the ScrollView hierarchy have a .contentShape(Rectangle()) modifier. By default, HStacks don't accept taps in between their child views, and depending on your child view's layout this can cause taps to be missed even when it looks like they should be landing. .contentShape(Rectangle()) makes the entire frame of the HStack tappable.

What mouse gesture to delete an item from a List on macOS in SwiftUI?

I don't understand how to delete or move a item on macOS. What action should I need to do with the mouse to trigger onDelete or onMove events?
#State var wishList = ["Item 1", "Item 2", "Item3"]
var body: some View {
List {
ForEach(wishList, id:\.self) { item in
Button(action: {
}) {
Text(item)
}
}
.onDelete { offsets in
}
.onMove { source, target in
}
}
}
Move:
Click and drag rows.
Delete:
Swipe with (two fingers on trackpad or one finger on magic mouse), like the way you scroll horizontally.
Note that you should NOT click and drag the row like the way you swipe in iOS simulator. Just a simple mac horizontal scroll is enough.
These gestures won't work if you have an ordinary two-button mouse. You will need to make affordances in your UI for people with simpler mice.

How to create a popup menu below navigationitems in swiftui

I want to create a popup menu like this
and it has a clear full screen overlay, when touch the overlay, popup menu will dismiss.
I tried add an overlay to root view, and add a menu list view to it, hardcoding position and frame for it, align with navigationItem then create a EnvironmentObject to store the overlay's toggle.
After this, I arrived my goal, but I think it was kind of mechanical, so my question is, is there has a good way to do this? like just use view modifier, or another with less step?
This is my root view:
struct Root : View {
TabbedView {
NavigationView {
HomePage()
}
}.overlay(...) <-- add a overlay at root view.
}
struct HomePage : View {
var body: some View {
ZStack {
List {...}
}
.navigationBarTitle("Home")
.navigationBarItems(trailing:
Button(action: {
// show popup menu
}) {
Image(systemName: "plus.circle")
}
)
}
}
I think this answer can help you. there have showing popUp using zstack.
Possibly a duplicate of >
Present Modal fullscreem SwiftUI