I have a json file where I can display all the contents from it in a view.
struct Test: View{
let quizzs = Bundle.main.decode([Quizz].self, from: "capitals.json")
var body: some View{
ScrollView {
VStack{
ForEach(quizzs!, id: \.self){ item in
Text(item.country)
}
}
}
}
}
But I want to be able to display a country of index[n]
I tried doing this:
#State var index = 0
Text(item.country[index])
But I get this error:
Cannot convert value of type 'Int' to expected argument type 'Range<String.Index>'
How can I make it possible so that I can get a country at index n?
It seems each item has the same country of the array structure. So you can use the index to get the corresponding country.
Try the following ways of writing to meet your needs.
var body: some View {
ScrollView {
VStack {
ForEach(Array(quizzs.enumerated()), id: \.self) { index, item in
Text("\(item.country[index])")
}
}
}
}
}
Related
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!
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.
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.
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
})
}
}
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.