How to select just one Toggle in LazyVGrid - swiftui

I use LazyVGrid to show set of data and as one column I'd like to use Toggle view to allow multiple lines selection. If I do it like this
ScrollView {
LazyVGrid(columns: columns) {
ForEach(data, id:\.id) { record in
Toggle("", isOn: $selected).onChange(of: selected) { sel in
print(record.id!)
}
Text("\(dateFormatter.string(from: record.start!))")
}
}
}
then (of course) all rows are selected or unselected being all binded to selected #State var. What's best practice here to allow individual lines selection?
Thanks.

Related

How to trigger a NavigationLink programmatically in a LazyVGrid

I have a LazyVGrid inside a NavigationView.
NavigationView {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(items) { item in
NavigationLink(tag: item, selection: $displayedItem) {
DetailView(item)
} label: {
GridItemView(item)
}
}
}
}
}
The referenced variables are defined as follows on the view:
#State var displayedItem: Item?
let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2)
Now I want to show the detail view for a specific item. I do this by simply assigning this item to the displayedItem property:
func showDetailView(for item: Item) {
displayedItem = item
}
This works great when the respective item is visible on the LazyVGrid at the moment when I call this function. However, when the item is not visible, I first need to scroll to the item for the NavigationLink to fire. I know why this is happening (because the items are loaded lazily, it's a lazy grid after all), but I don't know how to make the LazyVGrid load the specific item when I need it.
What I've tried:
I have also tried to programmatically scroll to the target item by wrapping the entire ScrollView inside a ScrollViewReader and appending the following modifier:
.onChange(of: displayedItem) { item in
if let item = item {
scrollProxy.scrollTo(item.id)
}
}
Unfortunately, this has the same problem: Scrolling to a given item doesn't work until the item is loaded.
Question:
Is there any way to make this work, i.e. to trigger a NavigationLink for an item that is not currently visible in the LazyVGrid? (It's important for me as I need this functionality to deep-link to a specific item's DetailView.)
An possible approach can be like in this topic - use one link somewhere in background of ScrollView and activate it by tapGesture/button from user or assigning corresponding value programmatically.
Tested with Xcode 13.4 / iOS 15.5
Main part:
ScrollView {
LazyVGrid(columns: columns) {
ForEach(items) { item in
RoundedRectangle(cornerRadius: 16).fill(.yellow)
.frame(maxWidth: .infinity).aspectRatio(1, contentMode: .fit)
.overlay(Text("Item \(item.value)"))
.onTapGesture {
selectedItem = item
}
}
}
}
.padding(.horizontal)
.background(
NavigationLink(destination: DetailView(item: selectedItem), isActive: isActive) {
EmptyView()
}
)
.toolbar {
Button("Random") { selectedItem = items.randomElement() }
}
Test module on GitHub

SwiftUI on macOS - using Picker for each row in a Table Column

I am displaying some items in a Table on macOS (new in SwiftUI 3.0). I can get the table to work fine when displaying Text views in each column, but I really want to show a Picker view in one of the columns. But I'm not sure how I would 'bind' the selection of the picker to an item in the array that I use to drive the list of items.
Here's the code I'm trying:
struct TestSwiftUIView: View {
#State var mappingFields: [ImportMappingField]
var body: some View {
VStack {
Table(mappingFields) {
TableColumn("Imported Field") { item in
Text("\(item.columnName)")
}.width(160)
TableColumn("Mapped Field") { item in
Text("\(item.fieldName.typeNameAndDescription.description)")
}.width(150)
TableColumn("Type") { item in
Picker("", selection: $item.selectedCustomFieldType){ // error here
ForEach(ImportMappingType.allCases, id:\.self ) { i in
Text(i)
}
}
}.width(100)
}
}
}
}
In selection: $item.selectedCustomFieldType I get an error that says "Cannot find '$item' in scope".
So I want to bind each picker to an element in each 'item', but it doesn't let me do that.
Is there a good solution for this problem?
try using this approach to give you the item binding you need:
Table($mappingFields) { // <-- here
TableColumn("Type") { $item in // <-- here
Picker("", selection: $item.selectedCustomFieldType){
...
}
similarly for the other TableColumn. You may have to make ImportMappingField conform to Identifiable

How do I set the selected row background color in a SwiftUI List?

I am using a standard List in SwiftUI and would like to modify the selected rows' background color.
Here is the code:
...
List {
ForEach(recipesToShow, id: \.self) { recipe in
NavigationLink(destination: RecipeDetailWrapper(recipe: recipe)) {
RecipeListRow(recipe: recipe)
.environmentObject(recipeDateFormatter)
}
}
.onDelete(perform: deleteRecipe)
} //: LIST
.listStyle(PlainListStyle())
...
This is how it looks by default:
you can use the modifier .listRowBackground(Color.blue) to modify the background color of your list.
For the specific item, I would add a state var to store the id of the element tapped and then evaluate it in each item of the list,
you can check the answer here:
How do you change the select color of a navigation link in a list
that I think is what you need.

SwiftUI: Alternatives to using ListView?

I want to have a scrollable list that when each row is tapped, it takes you to a different view. Inside each row, I want to have a heart button that when tapped overrides the navigation behavior and just toggles a heart fill/unfill.
As an alternative to do this, would it make sense to use a ScrollView inside a NavigationView and then have each list item be a VStack?
Pseudocode hierarchy:
NavigationView {
ScrollView {
VStack {
// Button
}
}
}
Is there a better way ( or more preferred way) to accomplish this?
Use LazyVStack
let items = ["A","B"]
var body: some View {
ScrollView {
LazyVStack(spacing: 10) {
ForEach(items, id: \.self) { item in
Text(item)
}
}
}
}

How to make two Lists in one View scroll as one page in SwiftUI?

I'm trying to add two tables with different sizes to one view in SwiftUI. I want both tables be fully expanded and the whole view to scroll as one. For now I only get both tables fixed size and scroll separately.
some View {
VStack {
Text("Table foo")
List(foo { item in Text(item.name) })
Text("Table bar")
List(bar { item in Text(item.name) })
}
}
Tried changing VStack to a ScrollView, it makes things even worse - tables are becoming one liners.
Also tried replacing List() with ForEach() but then I lose features of the List() I actually want.
I think you want one List with multiple ForEach's. Optionally, you could make it a grouped List with multiple sections. Adapted for your sample, the following concept has worked well for me on several occasions:
struct SwiftUIView: View {
var body: some View {
List {
Section(header: Text("Table foo"))
{
ForEach(foo) { item in Text(item.name) }
}
Section(header: Text("Table bar"))
{
ForEach(bar) { item in Text(item.name) }
}
}
.listStyle(GroupedListStyle())
}
}
You can use Form to embed two lists in a scroll view.
Reference: https://developer.apple.com/documentation/swiftui/form .