How to iterate over an array in a LazyVGrid - swiftui

I am trying to iterate over an array of objects inside a LazyVGrid so as to print out the first name of each contact into each cell. The issue is, I am getting error: "Type '()' cannot conform to 'View'" on LazyVGrid...
After looking up potential fixes, I am seeing that some are saying we can't use functional code in a grid, but apple is using functional code in their documentation on how to use a LazyVGrid, so I am not understanding how this is true.
Does anyone know what I am missing?
Please see below code:
var body: some View {
NavigationStack {
VStack {
ScrollView {
LazyVGrid(columns: threeColumnGrid) {
// employeeContactsTempData is simply an array of objects containing contact related data
employeeContactsTempData.forEach({ contact in
VStack {
Text(contact.firstName)
}
})
}
}
Spacer()
}.background(.blue)
}
}

You should use ForEach to iterate over an array to create views.
LazyVGrid(columns: threeColumnGrid) {
// Use id argument if your contact model is not identifiable.
ForEach(employeeContactsTempData, id: \.self) { contact in
VStack {
Text(contact.firstName)
}
}
}

Related

#Binding to an element of an array that must be reinitialized

Sorry if I did something wrong but it's my first time using stackoverflow to ask something.
You will se a View used to represent a Shot variable, which is part of a Shot Array.
I will have as many Row's View as the Array number of elements, to display in a List.
After different tries, leaving the variable shot as #State, I noticed that the TextField did not change the shotName stored value of the single element of the Array, but using Binding it does.
And now comes the problem, in the view where I display all this Row's View, I have at some point to reinitialize the Shot array shots = [Shot](), to create a new one appending new Values.
As soon as I do that, I got an Array out of bound error, during execution, that comes from the reinitialization of the shot array, because I suppose, the binding value loses its variable (?)
PS. i noticed that I did not get the error after the first reinitialization but after the second one
Unfortunately I must reinitialize that array, and so I don't really know how to solve that problem.
struct ShotRowView: View {
#Binding var shot: Roll.Shot
var body: some View {
HStack {
Text("\(shot.id)")
.font(.title)
TextField(
"",
text: $shot.shotName,
prompt: Text("Shot Name")
)
.font(.title)
.autocorrectionDisabled()
Text("\(shot.date)")
.font(.body)
ShotImageView(image: shot.image)
}
}
}
Here's how I use this secondary view in my primary view:
#State var shots: Array<Roll.Shot> = [Roll.Shot]()
private func viewRoll() -> some View{
VStack {
if showPopUp {
RollPopUp(view: self)
}else{
NavigationView(){
List {
ForEach($shots){shot in
NavigationLink {
LogoView()
} label: {
ShotRowView(shot: shot)
}
}
.onDelete(perform: removeShot)
}
.padding([.top, .leading, .trailing])
.toolbar {
ToolbarItem (placement: .automatic) {
HStack {
}
}
}
}
}
}
}

SwiftUI: Using List and ForEach to display model data of different types with an array of Strings

I want to display a dynamic list of items that come from model data. The problem I have is how to properly use ForEach to display a var that is an array of Strings. There are no compiling issues with the code, but I cannot get the List to display.
Here is the struct for the model data:
struct OrderInfo: Codable, Identifiable, Hashable {
var id: Int
var orderNumber: String
var activeOrderStatus: Bool
var pickupSection: Int
var pickupStand: String
var pickupItems: [String]
}
Here is the code for my View:
struct CompletedOrderPickUp: View {
var completedOrder: OrderInfo
var body: some View {
ScrollView {
VStack {
HStack {
Text("Section")
Spacer()
Text("\(completedOrder.pickupSection)")
}
.pickUpDetailsTitleModifier()
HStack {
Text("Stand Name")
Spacer()
Text(completedOrder.pickupStand)
}
.pickUpDetailsTitleModifier()
HStack {
Text("Order Items")
Spacer()
}
.pickUpDetailsTitleModifier()
List {
ForEach(completedOrder.pickupItems, id: \.self) { items in
Text("\(items)")
Text(items)
}
}
}
}
}
}
And here is the screenshot of what it produces:
Screenshot with no items under "Order Items"
There's no issue with accessing the two other variables (pickupSection and pickupStand), which leads me to believe the problem lies with how to properly access the array of Strings within the data for pickupItems.
My friend Google has lots to say about ForEach and accessing data but not a lot about the combination of the two. Please let me know if I may clarify my question. Any help is much appreciated!

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

SwiftUI List is not showing any items

I want to use NavigationView together with the ScrollView, but I am not seeing List items.
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView{
VStack {
Text("Some stuff 1")
List{
Text("one").padding()
Text("two").padding()
Text("three").padding()
}
Text("Some stuff 2")
}
}
}
}
}
All I see is the text. If I remove ScrollView I see it all, but the text is being pushed to the very bottom. I simply want to be able to add List and Views in a nice scrollable page.
The ScrollView expects dimension from content, but List expects dimension from container - as you see there is conflict, so size for list is undefined, and a result rendering engine just drop it to avoid disambiguty.
The solution is to define some size to List, depending of your needs, so ScrollView would now how to lay out it, so scroll view could scroll entire content and list could scroll internal content.
Eg.
struct ContentView: View {
#Environment(\.defaultMinListRowHeight) var minRowHeight
var body: some View {
NavigationView {
ScrollView{
VStack {
Text("Some stuff 1")
List {
Text("one").padding()
Text("two").padding()
Text("three").padding()
}.frame(minHeight: minRowHeight * 3).border(Color.red)
Text("Some stuff 2")
}
}
}
}
}
Just wanted to throw out an answer that fixed what I was seeing very similar to the original problem - I had put a Label() item ahead of my List{ ... } section, and when I deleted that Label() { } I was able to see my List content again. Possibly List is buggy with other items surrounding it (Xcode 13 Beta 5).

Problem passing value with SwiftUI List(), ForEach(), .onTapGesture WatchOS6

I am trying to use the .onTapGesture() to call a method on the tapped view and pass some kind of ID or any other form of identifier, but i dont know how to.
i have a view ListView that i create from data that i get from my array trackPreviewList: [TrackpreviewList].
The view looks like this:
struct ListView: View {
#ObservedObject var model: PlaybackService
#State var list = PlaybackService.sharedInstance.trackPreviewList
var body: some View {
List {
Section(header: Text("Songs")) {
ForEach(model.trackPreviewList!, id: \.id) {
Text($0.artistName + ": " + $0.name)
}
.onDelete(perform: deleteTrack)
.onTapGesture(perform: skipTo)
}
}
}
I am very new to SwiftUI and coding in general, but i think the right approach is to say cell(view?) 2 was tapped, therefore pass 2 to the method func playerSkipTo(skipToIndex: Int) I have tried to hardcode the value 2 like this and it works with the hardcoded value:
Though i am clueless when it comes to how to pass some kind of identifier, i think my problem is that i am trying to access the object outside of the scope where the method "knows about it".
I have tried to use the $0.name variable just to see if i could pass anything but i get this error Contextual closure type '() -> Void' expects 0 arguments, but 1 was used in closure body which i think is because the .onTapGesture() method does not have a closure?.
I have tried to use the same logic as the .onDelete() method using IndexSet like this:
The method that i want to pass the value to in the end looks like this:
func playerSkipTo(skipToIndex: Int) {
if skipToIndex < ((trackPreviewList!.underestimatedCount)-1) {
_ = firstly {
TracklistProvider.sharedInstance.getPlaybackURL(trackPreview: trackPreviewList![skipToIndex])
}.map { playbackoptions in
self.createPlayerItemFromLink(assetURL: (playbackoptions?.playbackUrl)!)
}.done{
self.replacePlayerItem()
}
} else {
print("The index \(skipToIndex) is out of bounds")
}
}
I am aware that i will need to change the playerSkipTo() method to take in IndexSet also, which i have also tried to play around with, trying to cast it to Int and such.
Any help or pointers are greatly appreciated!
Heres the playerSkipTo method:
if skipToIndex < ((trackPreviewList!.underestimatedCount)-1) {
_ = firstly {
TracklistProvider.sharedInstance.getPlaybackURL(trackPreview: trackPreviewList![skipToIndex])
}.map { playbackoptions in
self.createPlayerItemFromLink(assetURL: (playbackoptions?.playbackUrl)!)
}.done{
self.replacePlayerItem()
}
} else {
print("The index \(skipToIndex) is out of bounds")
}
}
Instead of skipping to an index, skip to a specific track object:
func playerSkipTo(_ playbackoptions: PlaybackOptions) {
// ...
}
Then change your View code to call this function on the Text instead of the ForEach:
List {
Section(header: Text("Songs")) {
ForEach(model.trackPreviewList!, id: \.id) {
Text($0.artistName + ": " + $0.name)
.onTapGesture {
self.playerSkipTo($0)
}
}
}
}
You can use a button on top of a ZStack:
List{
ForEach(appData.assets) { asset in
ZStack {
HStack{
VStack(alignment: .leading) {
Text(asset.value)
.font(.headline)
}
Spacer()
}
Button(action: {assetPressed(id: asset.id)}, label: {
Text("")
})
}
}
.onDelete(perform: deleteAsset)
}