SwiftUI pick a value from a list with ontap gesture - swift

i'm try to pick some value from a swiftUI list with the ontapGesture.
I have a Searchlist of item, the user need to choose one item and then the app will send the choice to an array that later will be use for other info.
now my problem is how do I do that? how can do it?
as you can see from the code below, I want to pick the value of the item.icaoAirport corresponding to that raw and pass to an array.
List(dm.vettoreAeroporti.filter{
// $0.icaoCode.contains(searchTerm)
$0.icaoAirport.localizedCaseInsensitiveContains(searchTerm)
}) { item in
HStack {
Text(item.icaoAirport).bold()
Spacer()
Text(item.nameAirport)
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.dm.openFileJson(fileName: "data")
}
}
.onTapGesture {
// ?? I need to take the value of the item.icaoAirport corresponding to that raw
}
thanks in advance for the help.

While your, Damiano, answer is right it works only on tap on the Text.
Sometimes it is needed to have the whole row tappable, so here is the solution for this case:
List(items) { item in
HStack {
Text(item.icaoAirport).bold()
Spacer()
Text(item.nameAirport)
}
.contentShape(Rectangle())
.onTapGesture {
print("touched item \(item)")
}
}
Thanks Paul for this (https://www.hackingwithswift.com/quick-start/swiftui/how-to-control-the-tappable-area-of-a-view-using-contentshape)
Note, that if you have content only on one side of the HStack e.g:
HStack {
Text("Some text") // This text will be on the left side
}
then the .contentShape(Rectange()) will work only for the width of the text. So to enable it for the whole width just add a trailing Spacer() like this:
HStack {
Text("Some text")
Spacer() // This will take all the space from the end of the text up to the end of the whole row
}
.contentShape(Rectangle())

I solved my issue:
HStack {
Text(item.icaoAirport).bold()
Spacer()
Text(item.nameAirport)
.onTapGesture {
print("touched item \(item.icaoAirport)")
}
}

List {
ForEach(Type.allCases, id: \.self) { type in
FilterRow(Color: type.Color,
title: type.Title)
.contentShape(Rectangle())
.onTapGesture {
print("Ammu type:\(type)")
}
}
}

This one is quite old but the accepted answers are pretty strange. I was able to achieve this effect in a much simpler way. Consider:
List {
ForEach(myItems) { item in
Button {
// action
} label: {
Text(item.name)
}
}
}
Now doing it this way, the button is edge-to-edge in the row. However, the button is now the accent color of your app. That's probably not what you want! So I tried adding:
Button(...)
.buttonStyle(PlainButtonStyle())
The text of the button is now the primary foreground color, great! But there's a different problem now! The button no longer goes edge-to-edge, it's only the text. To fix this, i instead:
Button(...)
.foregroundColor(.primary)
This is edge-to-edge with primary color text and custom actions. No need to create HStacks with spacers and an onTap, it's all just handled. OP's post has text on both sides so likely still needs an HStack for their specific usecase but a Button is still probably a better choice.

Related

How do we control the automatic dividers in SwiftUI forms?

I'm curious, is there a way we can control (or make sense of) the automatic dividers that swiftui puts between items?
Here's some code showing strange behaviour (on iOS simulator):
struct FormSeparator: View {
#State private var passwd = ""
var body: some View {
Form {
ZStack(alignment: .trailing) {
TextField("password", text:$passwd)
Text("8 chars min")
.font(.caption)
.foregroundColor(.secondary)
}
TextField("confirm", text:$passwd)
}
}
}
Here's the result:
This seems to have something to do with the Text that I'm stacking on top of the password field. If I change it the Text to Image(systemName: "lock") then everything works as you'd expect:
Mind you, I don't even understand why it doesn't extend all the way across the form in that case. Presumably this is deliberate, has anyone seen anything official about this situation? Do we know how to control this?
In iOS 16, List/Form row separator insets automatically and aligns with the text. In iOS 16, we got two new alignments, listRowSeparatorLeading and listRowSeparatorTrailing, to work with alignmentGuide(_:computeValue:).
ZStack(alignment: .trailing) {
TextField("password", text:$passwd)
Text("8 chars min")
.font(.caption)
.foregroundColor(.secondary)
}
.alignmentGuide(.listRowSeparatorLeading) { viewDimensions in
return 0
}

Better way of handling Tap Gesture on Lists

I'm trying to implement a 1 count tap gesture and 2 count tap gesture on rows of a list view.
Currently I'm achieving this by expanding the contents of the rows to fill the entire width and height, by changing the row insets with .listRowInsets(EdgeInsets()) and adding an HStack with a Spacer inside. As seen below:
My ListRow View, which serves to attempt to fill out the the entire rows' width and height. (With a red border for debugging):
struct ListRow<Content: View> : View {
let content: () -> Content
var body: some View {
HStack {
content()
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.border(Color.red)
}
}
And here's a snippet of one of the two places I'm using a list with my ListRow:
List(selection: $selectedItem) {
Section(header: Text("Favourites")) {
ForEach($sidebarItems, id: \.self) { $item in
ListRow {
FileLabel(URL(string: item.path)!, size: 14, text: item.text)
}.gesture(TapGesture().onEnded {
selectedItem = item
fileState.updatePath(path: item.path)
}).listRowInsets(EdgeInsets())
}
}
}.listStyle(SidebarListStyle())
A few issues happen here, which both feels hacky and incorrect to do.
I have to manually select the row when the content is tapped.
I actually manually have to focus the list, if it's not actually focused when clicking a row, otherwise the selection is a very faint color.
The contents aren't actually expanding to the leading and trailing edges of the row, no matter if I give my EdgeInsets negative values (This just results in the contents getting clipped, rather than expanding to the edge).
Clicking the areas outside of the red border (seen below) results in the row being selected, but the tap action not firing.
Preferably I'd love if there was a way to listen for tap gestures on a list and get the row that was tapped. It would fix having to manually reimplement features that the List already has built-in and lowering the risk of introducing bugs + it just feels like the right way to do it, unfortunately I haven't been able to find other ways of implementing this, other than creating an HStack with a Spacer.
EDIT:
I'm curious as to how NavigationLink works as no matter where you click, or if you navigate using your keyboard, it'll actually trigger the navigation. Makes me think there's some kind of binding or event fired?

Issue with giving a title to a menu

I want to give a text name for this menu but it won't let me saying there's an Extra argument in call what's wrong ? PS: I'm a beginner in coding
Divider()
Menu("\(buttonTitle)" ) {
Button("Riyadh",action: {SelectRiyadh()})
Button("Dammam",action: {SelectDammam()})
Button("Jeddah",action: {SelectJeddah()})
Button("Mecca",action: {SelectMecca()})
}
We need to see the ContentView page, but try to wrap all that code in some group or into stack, for example VStack.
Anyway, read this article first: How to show a menu when a button is pressed
VStack{
Divider()
Menu("\(buttonTitle)" ) {
Button("Riyadh",action: {option()})
}
}

Swift List looks too big

I have a List in my App. When I click on an Item in the List it opens another List. The First List looks fine, but the second List looks too big. Here is how it looks normal(good):
And here is the List that looks not good:
On the second Picture, the Problems I have are:
the title is too big
the list item starts too far away from the title
Thats the Code, the Code looks the same in both Lists. The only difference is that I used some different variable names in the second one.
return VStack {
ZStack {
NavigationView {
List {
ForEach(zettelArr) { x in
NavigationLink(destination: ZettelViewDetails(passedVar: x)) {
Text("\(x.name)")
}
}
}.navigationBarTitle(alertVariable)
.navigationBarItems(trailing: Button(action: {
alertView()
//self.isShown = true
}) {
Image(systemName: "plus")
})
}
//SwiftUIAlertViewWithTextBox(isShown: $isShown, message: $msg, title: $title)
}
}
My goal is, that the second List looks exactly like the Main List.
How is this possible ?
Okay I don't know why the formatting from the pictures are not working, maybe I am just dumb. The first Picture is the MainList that looks like I want it to look and the second one is the list that is formatted differently, even though its quite the same Code.
You are using NavigationView for both lists view , while only the first one should be inside a NavigationView
Your ZettelViewDetails shouldn't have NavigationView as parent
Example :
Main
NavigationView {
List {
ForEach(zettelArr) { x in
NavigationLink(destination: ZettelViewDetails(passedVar: x))
{
Text("\(x.name)")
}
}
}.navigationBarTitle(alertVariable)
}
Details
VStack {
ForEach(zettelArr) { x in
NavigationLink(destination: ZettelViewDetails(passedVar: x)) {
Text("\(x.name)")
}
}
}

SwiftUI does not perform onInsert when the list is empty?

I have 2 lists on the UI, 1 list is allow user to drag to another list. I have problem when the
"selected product list" is empty. It does not perform the .onInsert function.
It have no problem when the array "selectedProduct" is not empty when it was rendering.
How can i to allow user to drag and drop when the other list is completely empty?
List(selection: $selection)
{
HStack(alignment: .center)
{
Spacer().frame(width: 200)
Text("Product Name")
.foregroundColor(blue)
.lineLimit(1)
Spacer()
Text("Price")
.foregroundColor(blue)
Spacer()
Text("Unit")
.foregroundColor(blue)
Spacer()
}
ForEach(selectedProduct, id:\.id)
{
rider in
SelectedProductView()
}
.onInsert(of: ["public.data"])
{
self.addProduct(position: $0, itemProviders: $1, top: true)
}
}
The .onInsert works on DynamicViewContent, so when your selectedProduct is empty there is no content, so nowhere to insert.
There two possibilities as I see to solve this situation:
1) implement support for drop on List and make it active when selectedProduct is empty to make possible to add first element in dynamic content
2) implement stub product-item (like, empty row showing "drop here" text) that is explicitly added to selectedProduct when it becomes empty of real products, thus making content not empty to allow standard .onInsert work for you.