how to insert a structured identifiable array in a list? - swiftui

struct Gg: Identifiable{
let id: Int
let task: String
}
struct ContentView: View {
#State private var items = [Gg(id: 1, task:"take the trash out"), Gg(id: 2, task:"Go for a run")]
var body: some View {
NavigationView {
ZStack(alignment: .center) {
VStack {
List(self.items, id: \.self) { index in
Text("\(index)")
}
}}
I get the following error
Cannot declare entity named '$id'; the '$' prefix is reserved for implicitly-synthesized declarations
Initializer 'init(_:id:rowContent:)' requires that 'Gg' conform to 'Hashable'
quit new and starting out appreciate the help

You iterate by item, not by indices, so it is simply
VStack {
List(self.items) { item in
Text(item.task)
}

Related

Unsafe Pointer?

I am receiving this error message.
I am trying to make a subview that I can reuses to display a row in my app.
For some reason, it is pointing to the index variable that I am using to iterate over my enum in my List to display my data.
Why is this happening? I am not sure how to refer to this variable outside of the subview.
struct DisplayRow: View {
var name: String
var code: String
var value: Double
var counter: Int
var body: some View {
List(0..<counter, id: \.self) {index in
VStack {
HStack {
Text(name)
Text(code)
}
Text("$\(value)")
}
}
}
}
struct ContentView: View {
var body: some View {
List {
Section(header: Text("Wounds")) {
DisplayWoundRow()
}
Section(header: Text("Debride")) {
DisplayRow(name: debride.allCases[index].rawValue, code: debridecode.allCases[index].rawValue, value: debridevalue.allCases[index].rawValue, counter: debride.allCases.count)
}
}
You need ForEach for that, like
Section(header: Text("Debride")) {
ForEach(0..<debride.allCases.count, id: \.self) { index in // << here !!
DisplayRow(name: debride.allCases[index].rawValue, code: debridecode.allCases[index].rawValue, value: debridevalue.allCases[index].rawValue, counter: debride.allCases.count)
}
}
*I don't have all those types and cannot test, so typos or other errors are on you.

How to redraw a child view in SwiftUI?

I have a ContentView that has a state variable "count". When its value is changed, the number of stars in the child view should be updated.
struct ContentView: View {
#State var count: Int = 5
var body: some View {
VStack {
Stepper("Count", value: $count, in: 0...10)
StarView(count: $count)
}
.padding()
}
}
struct StarView: View {
#Binding var count: Int
var body: some View {
HStack {
ForEach(0..<count) { i in
Image(systemName: "star")
}
}
}
}
I know why the number of stars are not changed in the child view, but I don't know how to fix it because the child view is in a package that I cannot modify. How can I achieve my goal only by changing the ContentView?
You are using the incorrect ForEach initializer, because you aren't explicitly specifying an id. This initializer is only for constant data, AKA a constant range - which this data isn't since count changes.
The documentation for the initializer you are currently using:
It's important that the id of a data element doesn't change unless you
replace the data element with a new data element that has a new
identity. If the id of a data element changes, the content view
generated from that data element loses any current state and animations.
Explicitly set the id like so, by adding id: \.self:
struct StarView: View {
#Binding var count: Int
var body: some View {
HStack {
ForEach(0 ..< count, id: \.self) { _ in
Image(systemName: "star")
}
}
}
}
Similar answer here.

Same ForEach loop twice in one SwiftUI View

When I use a ForEach loop over an array twice within a view, I get the following warning at runtime:
LazyVGridLayout: the ID 84308994-9D16-48D2-975E-DC40C5F9EFFF is used by multiple child views, this will give undefined results!
The reason for this is clear so far, but what is the smartest way to work around this problem?
The following sample code illustrates the problem:
import SwiftUI
// MARK: - Model
class Data: ObservableObject
{
#Published var items: [Item] = [Item(), Item(), Item()]
}
struct Item: Identifiable
{
let id = UUID()
var name: String = ""
var description: String = ""
}
// MARK: - View
struct MainView: View {
#StateObject var data: Data
private var gridItems: [GridItem] { Array(repeating: GridItem(), count: data.items.count) }
var body: some View {
LazyVGrid(columns: gridItems, alignment: .leading, spacing: 2) {
ForEach(data.items) { item in
Text(item.name)
}
ForEach(data.items) { item in
Text(item.description)
}
}
}
}
// MARK: - App
#main
struct SwiftUI_TestApp: App {
var body: some Scene {
WindowGroup {
MainView(data: Data())
}
}
}
I could possibly divide the view into several SubViews.
Are there any other options?
Edit:
This is the body of the real app:
var body: some View {
VStack {
LazyVGrid(columns: columns, alignment: .leading, spacing: 2) {
Text("")
ForEach($runde.players) { $player in
PlayerHeader(player: $player)
}
ForEach(Score.Index.allCases) { index in
Text(index.localizedName)
ForEach(runde.players) { player in
Cell(player: player, active: player == runde.activePlayer, index: index)
}
}
Text ("")
ForEach(runde.players) { player in
PlaceView(player: player)
}
}
.padding()
}
}
If you really need that kind of grid filling, then it is possible just to use different identifiers for those ForEach containers, like
LazyVGrid(columns: gridItems, alignment: .leading, spacing: 2) {
ForEach(data.items) { item in
Text(item.name).id("\(item.id)-1") // << here !!
}
ForEach(data.items) { item in
Text(item.description).id("\(item.id)-2") // << here !!
}
}
Tested with Xcode 13beta / iOS 15
While adding identifiers within the ForEach loop sometimes works, I found that accessing the indexes from the loop with indices worked in other cases:
ForEach(items.indices, id: \.self) { i in
Text(items[i])
}

SwiftUI: onDelete doesn't update UI correctly

When I delete an element of an array using onDelete(), it removes the correct item in the data but removes the last item on the UI. I saw this answer but I am already using what it recommends. Any advice?
struct ContentView: View {
#State var items = ["One", "Two", "Three"]
var body: some View {
Form{
ForEach(items.indices, id:\.self){ itemIndex in
let item = self.items[itemIndex]
EditorView(container: self.$items, index: itemIndex, text: item)
}.onDelete(perform: { indexSet in
self.items.remove(atOffsets: indexSet)
})
}
}
}
Here is the EditorView struct:
struct EditorView : View {
var container: Binding<[String]>
var index: Int
#State var text: String
var body: some View {
TextField("Type response here", text: self.$text, onCommit: {
self.container.wrappedValue[self.index] = self.text
})
}
}
Your ForEach is looping over the indices of your array, so that is what SwiftUI is using to identify them. When you delete an item, you have one fewer index for the array, so SwiftUI interprets that as the last one has been deleted.
To do this correctly, you should be looping over a list of Identifiable items that have a unique id. Here I've created a struct called MyItem which holds the original String and a uniquely generated id. I use .map(MyItem.init) to convert the items into a [MyItem].
Also, your code needs the index in the loop, so loop over Array(items.enumerated()) which will give you an array of (offset, element) tuples. Then tell SwiftUI to use \.element.id as the id.
Note that EditorView now takes an Array of MyItem.
With these changes, SwiftUI will be able to identify the item you have deleted from the list and update the UI correctly.
struct MyItem: Identifiable {
var name: String
let id = UUID()
}
struct ContentView: View {
#State var items = ["One", "Two", "Three"].map(MyItem.init)
var body: some View {
Form{
ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
EditorView(container: self.$items, index: index, text: item.name)
}.onDelete(perform: { indexSet in
self.items.remove(atOffsets: indexSet)
})
}
}
}
struct EditorView : View {
var container: Binding<[MyItem]>
var index: Int
#State var text: String
var body: some View {
TextField("Type response here", text: self.$text, onCommit: {
self.container.wrappedValue[self.index].name = self.text
})
}
}

Conditionally Text in SwiftUI depending on Array value

I want make placeholder custom style so i try to use the method of Mojtaba Hosseini in SwiftUI. How to change the placeholder color of the TextField?
if text.isEmpty {
Text("Placeholder")
.foregroundColor(.red)
}
but in my case, I use a foreach with a Array for make a list of Textfield and Display or not the Text for simulate the custom placeholder.
ForEach(self.ListeEquip.indices, id: \.self) { item in
ForEach(self.ListeJoueurs[item].indices, id: \.self){idx in
// if self.ListeJoueurs[O][O] work
if self.ListeJoueurs[item][index].isEmpty {
Text("Placeholder")
.foregroundColor(.red)
}
}
}
How I can use dynamic conditional with a foreach ?
Now I have a another problem :
i have this code :
struct EquipView: View {
#State var ListeJoueurs = [
["saoul", "Remi"],
["Paul", "Kevin"]
]
#State var ListeEquip:[String] = [
"Rocket", "sayans"
]
var body: some View {
VStack { // Added this
ForEach(self.ListeEquip.indices) { item in
BulleEquip(EquipName: item, ListeJoueurs: self.$ListeJoueurs, ListeEquip: self.$ListeEquip)
}
}
}
}
struct BulleEquip: View {
var EquipName = 0
#Binding var ListeJoueurs :[[String]]
#Binding var ListeEquip :[String]
var body: some View {
VStack{
VStack{
Text("Équipe \(EquipName+1)")
}
VStack { // Added this
ForEach(self.ListeJoueurs[EquipName].indices) { index in
ListeJoueurView(EquipNamed: self.EquipName, JoueurIndex: index, ListeJoueurs: self.$ListeJoueurs, ListeEquip: self.$ListeEquip)
}
HStack{
Button(action: {
self.ListeJoueurs[self.EquipName].append("") //problem here
}){
Text("button")
}
}
}
}
}
}
struct ListeJoueurView: View {
var EquipNamed = 0
var JoueurIndex = 0
#Binding var ListeJoueurs :[[String]]
#Binding var ListeEquip :[String]
var body: some View {
HStack{
Text("Joueur \(JoueurIndex+1)")
}
}
}
I can run the App but I have this error in console when I click the button :
ForEach, Int, ListeJoueurView> count (3) != its initial count (2). ForEach(_:content:) should only be used for constant data. Instead conform data to Identifiable or use ForEach(_:id:content:) and provide an explicit id!
Can someone enlighten me?
TL;DR
You need a VStack, HStack, List, etc outside each ForEach.
Updated
For the second part of your question, you need to change your ForEach to include the id parameter:
ForEach(self.ListeJoueurs[EquipName].indices, id: \.self)
If the data is not constant and the number of elements may change, you need to include the id: \.self so SwiftUI knows where to insert the new views.
Example
Here's some example code that demonstrates a working nested ForEach. I made up a data model that matches how you were trying to call it.
struct ContentView: View {
// You can ignore these, since you have your own data model
var ListeEquip: [Int] = Array(1...3)
var ListeJoueurs: [[String]] = []
// Just some random data strings, some of which are empty
init() {
ListeJoueurs = (1...4).map { _ in (1...4).map { _ in Bool.random() ? "Text" : "" } }
}
var body: some View {
VStack { // Added this
ForEach(self.ListeEquip.indices, id: \.self) { item in
VStack { // Added this
ForEach(self.ListeJoueurs[item].indices, id: \.self) { index in
if self.ListeJoueurs[item][index].isEmpty { // If string is blank
Text("Placeholder")
.foregroundColor(.red)
} else { // If string is not blank
Text(self.ListeJoueurs[item][index])
}
}
}.border(Color.black)
}
}
}
}
Explanation
Here's what Apple's documentation says about ForEach:
A structure that computes views on demand from an underlying collection of of [sic] identified data.
So something like
ForEach(0..2, id: \.self) { number in
Text(number.description)
}
is really just shorthand for
Text("0")
Text("1")
Text("2")
So your ForEach is making a bunch of views, but this syntax for declaring views is only valid inside a View like VStack, HStack, List, Group, etc. The technical reason is because these views have an init that looks like
init(..., #ViewBuilder content: () -> Content)
and that #ViewBuilder does some magic that allows this unique syntax.