Swiftui how to catch taps on menu item - swiftui

I'm trying to catch the tap on an item in a menu, but I can't get it to work.
I've tried something like this:
Menu("Tap me") {
ForEach (references, id: \.self) { ref in
Link(ref.name, destination: URL(string: ref.url))
.simultaneousGesture(TapGesture().onEnded{
onLinkTapped()
})
.onTapGesture{
onLinkTapped()
}
}
}
but I never get to the onLinkTapped(). If it's not inside a Menu, then it works, but not inside the menu. How to catch the tap gesture?

Just use Button inside Menu:
Menu("Tap me") {
ForEach (references, id: \.self) { ref in
Button {
onLinkTapped()
} label: {
Link(ref.name, destination: URL(string: ref.url))
}
}
}

Related

How do I get an "edit" button for a row in a list/for each loop? [SwiftUI]

I want to get an edit and add a button like the circled buttons shown in the image below. I need these buttons so that I can change the properties of the folders. How do I get this button to work when the default SwiftUI Edit button is clicked?
What I want:
(Before Edit button clicked)
(After Edit Button Clicked)
What I currently have:
(Before Edit button clicked)
(After Edit Button Clicked)
My code:
var body: some View {
ZStack {
List {
ForEach(searchResults, id: \.self.id) { dir in
Section(dir.title) {
ForEach(dir.getChildFolders(), id: \.self.id) { folder in
NavigationLink(destination: DirectoryView(directory: folder)) {
Label(folder.title, systemImage: "folder")
}
}
.onDelete(perform: { offsets in
dir.items.remove(atOffsets: offsets)
updateView.update()
})
.onMove(perform: { source, destination in
dir.items.move(fromOffsets: source, toOffset: destination)
updateView.update()
})
}
}
}
You need to display the button yourself.
First, ensure you have some #State var tracking whether Edit mode is toggled:
#State var editing: Bool = false
Then, in your body, show the circle button next to the NavigationLink if you are editing:
var body: some View {
ZStack {
List {
ForEach(searchResults, id: \.self.id) { dir in
Section(dir.title) {
ForEach(dir.getChildFolders(), id: \.self.id) { folder in
HStack {
NavigationLink(destination: DirectoryView(directory: folder)) {
Label(folder.title, systemImage: "folder")
}
if editing {
Spacer()
Button(action: {
// Perform circle button action here
}) {
Image(systemName: "ellipsis.circle")
}
}
}
}
.onDelete(perform: { offsets in
dir.items.remove(atOffsets: offsets)
updateView.update()
})
.onMove(perform: { source, destination in
dir.items.move(fromOffsets: source, toOffset: destination)
updateView.update()
})
}
}
}
}
}

Add button to picker label

I want to add a button to swiftui picker's label.
But the button is not clickable.
When I click on the button the picker is clicked.
How Do I make the picker take clicks only in the area of the selected value?
and the buttons take his clicks?
import SwiftUI
enum Animal: String, CaseIterable, Identifiable {
case dog
case cat
case bird
var id: String { self.rawValue }
}
struct ContentView: View {
#State private var selectedAnimal = Animal.dog
var body: some View {
Form {
Group {
Section(header: Text("Animales")) {
VStack{
Picker(
selection: $selectedAnimal,
content: {
ForEach(Animal.allCases, id:\.self) {
Text($0.rawValue)
}},
label: {
HStack {
Text ("Chose Animale")
Spacer ()
Button (
action: {
print ("clicked")
},
label: {
Image(systemName: "arrow.clockwise")
})
Spacer ()
}
}
)
}
}
}
}
}
}
To solve this issue we need to separate picker and button and block Form tracking click inside row (which is by default track entire row).
For first move button out of picker and place everything in HStack, for second we need couple of tricks like tapGesture on label and non-default button style for button (for simplicity I used primitive button style, but it's better to create custom with appropriate highlight, etc.)
Here is a simplified updated and tested your code (Xcode 13 / iOS 15):
var body: some View {
Form {
Group {
Section(header: Text("Animales")) {
HStack{
HStack {
Text ("Chose Animale")
Spacer ()
}
.contentShape(Rectangle())
.onTapGesture {
// just blocker for label click
}
.overlay(
Button (
action: {
print ("clicked")
},
label: {
Image(systemName: "arrow.clockwise").foregroundColor(.blue)
})
.buttonStyle(PlainButtonStyle()) // << needed custom !!
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.layoutPriority(1) // << to cover much area
//.border(Color.red) // << for testing area
Picker("",
selection: $selectedAnimal,
content: {
ForEach(Animal.allCases, id:\.self) {
Text($0.rawValue)
}}
)
.labelsHidden() // << hide own label
.fixedSize() // << limit size !!
}
.listRowInsets(EdgeInsets()) // << consume row space !!
}
}
}
}

Multiple sheet(item: ) triggered by SwipeActions button and ToolBar buttons gets nil object in the first time in SwiftUI

I am using swipeActions in ForEach loop of a List and toolbar buttons to show different sheets on my SwiftUI view. But every first time I swipe left and click the Edit button, the object of that line is nil. If I do the same swipe and click thing again, everything goes well. Anyone else had this kind of bug before? Thank you.
Here is the related code:
struct LanguagesView: View {
#State var activeSheet: ActiveSheet?
#State var toBeEdit: MeLanguage?
var body: some View {
NavigationView {
List {
ForEach(self.meLanguages, id: \.id) { lan in
HStack {
Text("\(lan.wrappedName)")
.font(.headline)
}.swipeActions(allowsFullSwipe: false) {
Button(
action: {
self.activeSheet = .editLanguage
self.toBeEdit = lan
},
label: { Label("Edit", systemImage: "pencil") }
) .tint(.indigo)
}
}
}
.sheet(item: $activeSheet,
onDismiss: {
self.toBeEdit = nil
}
){
item in
switch item {
case .addLanguage:
AddLanguage()
case .sortLanguages:
SortLanguagesView()
case .editLanguage:
if self.toBeEdit != nil {
EditLanguageView( meLanguage: self.toBeEdit! )
}
else {
Text("self.toBeEdit is nil")
}
default:
Text("No such button on ContentView.")
}
}
.toolbar {
ToolbarItemGroup {
HStack {
Text("\(self.meLanguages.count) Languages on Card").font(.headline)
self.barButtons
}
}
}
}
}
var barButtons: some View {
HStack {
Button(
action: {
self.activeSheet = .sortLanguages
},
label: { Label("Sort Languages", systemImage: "arrow.up.arrow.down.circle")
}
).id("sortLanguages")
Button(
action: {
self.activeSheet = .addLanguage
},
label: { Label("Add Language",
systemImage: "plus")
.imageScale(.large)
}
)
}
}
}
If I only think of the sheet triggered by swipeActions Edit button, the code below works perfectly. But I still need other sheets triggered by ToolBar buttons.
.sheet(item: self.$toBeEdit, content: { toBeEdit in
EditLanguageView( meLanguage: toBeEdit)
})
After more searching I realised it's not something about SwipeActions. This is actually similar to this question:
SwiftUI presenting sheet with Binding variable doesn't work when first presented
So I added an hidden Text after the ForEach loop and the problem solved.
ForEach(self.meLanguages, id: \.id) { ... }
if self.toBeEdit != nil {
Text("\(self.toBeEdit!.wrappedName)").hidden()
}

NavigationLink onTapGesture and navigation not firing consistently

In a SwiftUI view I implemented a vertically scrolling list by creating a VStack that contains a NavigationView that contains some text. I build this by looping through a popularFeedTypes object creating a NavigationLink for each item in the popularFeedTypes object. If a user clicks on the NavigationLink the user is pushed to a new view called FeedTypeView. You can see the code below.
VStack{
NavigationView{
List (self.popularFeedTypes.popularFeedTypes!, id: \.self) { feedType in
NavigationLink(destination: FeedTypeView(feedType: feedType)) {
Text(feedType.feedType)
}.onTapGesture {
print("TAPPED")
}
}
.navigationBarTitle(Text("Discover"), displayMode: .inline)
}
}
The problem I am experiencing is that if I have an onTapGesture action on the NavigationLink, I experience a different behavior from within the simulator depending upon how I click the row. If I click on the text or on the arrow (>) at the right hand side of the row, the onTapGesture fires off but no navigation occurs. If I click on the space between the text and the arrow, onTapGesture does not fire but navigation occurs. If I remove the onTapGesture code, clicking any of the three places causes navigation to occur. So my question is shouldn't navigation occur even with an onTapGesture action existing? Also shouldn't the onTapGesture action fire off regardless of where you click on the row that makes up the NavigationLink?
VStack {
NavigationView{
List (self.popularFeedTypes.popularFeedTypes!, id: \.self) { feedType in
NavigationLink(destination: FeedTypeView(feedType: feedType)) {
Text(feedType.feedType)
}
.simultaneousGesture(TapGesture().onEnded {
print("TAPPED")
})
.buttonStyle(PlainButtonStyle())
}
.navigationBarTitle(Text("Discover"), displayMode: .inline)
}
}
This should work. use simultaneousGesture on NavigationLink
You can use onAppear to handle tapped event.
VStack {
NavigationView {
List(self.popularFeedTypes.popularFeedTypes!, id: \.self) { feedType in
NavigationLink(
destination: FeedTypeView(feedType: feedType).onAppear { print("TAPPED") }) {
Text(feedType.feedType)
}
}
.navigationBarTitle(Text("Discover"), displayMode: .inline)
}
}
If you want to perform both action and navigation simultaneously you can do this:
VStack{
NavigationView{
List (self.popularFeedTypes.popularFeedTypes!, id: \.self) { feedType in
NavigationLink(destination: FeedTypeView(feedType: feedType)) {
Text(feedType.feedType)
}
.simultaneousGesture(TapGesture().onEnded{
print("TAPPED")
})
}
.navigationBarTitle(Text("Discover"), displayMode: .inline)
}
}
If you want to do something before your navigation link is triggered, you can initialize a navigation link with tag and selection.
struct someViewName: View {
#State var navigationLinkTriggerer: Bool? = nil
#State var navigationLinkFeedType: FeedType
var body: some View {
VStack {
NavigationLink(destination: FeedTypeView(feedType: navigationLinkFeedType),
tag: true,
selection: $navigationLinkTriggerer) {
EmptyView()
}
NavigationView {
List (self.popularFeedTypes.popularFeedTypes!, id: \.self) { feedType in
Button(feedType) {
// do all you want here
print("TAPPED")
// then set the navigationLinkTriggerer to value of you navigation link `tag`
// in this case tag is equal to `true`
// this will trigger the navigation link
self.navigationLinkFeedType = feedType
self.navigationLinkTriggerer = true
}
}
.navigationBarTitle(Text("Discover"), displayMode: .inline)
}
}
}
}

Button and List interaction modifications

I am trying to remove the "sectionStyle" for my List rows. In order to achieve a tableview-like view, you would write the following:
List(items, id: \.id) { item in
ItemRow(with: item)
}
If you want to interpret tap events on each row, you would wrap the ItemRow into a Button:
List(items, id: \.id) { item in
Button(action: rowTapped) {
ItemRow(item: item)
}
}
The interesting part is that when the List knows about the Button, it will automatically give a selection animation like tableView's selectionStyle. For my application, I'd like to not have that. Is there a way to set the "selectionStyle" for the List to "none"?
There are a number of List behaviours, that I haven't been able to alter. It's quite easy to build your own list though if you have specific needs different than the system List view. Here's a simple example that I've used.
struct MyListView:View {
var body: some View {
ScrollView {
ForEach(items, id: \.id) { item in
VStack {
Button(action: self.rowTapped) {
ItemRow(item: item)
}
Divider()
}
}
}.padding()
}
func rowTapped() {
print("rowTapped")
}
}
struct ItemRow:View {
var item:Item
var body: some View {
HStack {
Text(item.name)
Spacer() // force text to left justify
}
}
}