SwiftUI ForEach based on State int
from apple docs
extension ForEach where Data == Range<Int>, ID == Int, Content : View {
/// Creates an instance that computes views on demand over a *constant*
/// range.
///
/// This instance only reads the initial value of `data` and so it does not
/// need to identify views across updates.
///
/// To compute views on demand over a dynamic range use
/// `ForEach(_:id:content:)`.
public init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)
}
So, you have to use (as suggested by Apple)
struct ContentView: View {
@State var counter = 0
var body: some View {
VStack {
ForEach(0 ..< counter, id:\.self) { i in
Text("row: \(i.description)")
}
Button(action: {
self.counter += 1
}, label: {
Text("counter \(counter.description)")
})
}
}
}
Swift UI ForEach should only be used for *constant* data
Your problem is here:
ForEach(0..<card.numberOfShapes.rawValue) { index in
cardShape().frame(height: geometry.size.height/6)
}
This ForEach
initializer's first argument is a Range<Int>
. The documentation says (emphasis added):
The instance only reads the initial value of the provided data and doesn’t need to identify views across updates. To compute views on demand over a dynamic range, use
ForEach/init(_:id:content:)
.
Because it only reads “the initial value of the provided data”, it's only safe to use if the Range<Int>
is constant. If you change the range, the effect is unpredictable. So, starting in Xcode 13, the compiler emits a warning if you pass a non-constant range.
Now, maybe your range (0..<card.numberOfShapes.rawValue
) really is constant. But the compiler doesn't know that. (And based on your error, it's not a constant range.)
You can avoid the warning by switching to the initializer that takes an id:
argument:
ForEach(0..<card.numberOfShapes.rawValue, id: \.self) { index in
// ^^^^^^^^^^^^^
// add this
cardShape().frame(height: geometry.size.height/6)
}
ForEach - Index out of range?
Let me give you a simple example to show you what happened:
struct ContentView: View {
@State private var lowerBound: Int = 0
var body: some View {
ForEach(lowerBound..<11) { index in
Text(String(describing: index))
}
Button("update") { lowerBound = 5 }.padding()
}
}
if you look at the upper code you would see that I am initializing a ForEach JUST with a Range like this: lowerBound..<11
which it means this 0..<11
, when you do this you are telling SwiftUI, hey this is my range and it will not change! It is a constant Range! and SwiftUI says ok! if you are not going update upper or lower bound you can use ForEach without showing or given id
! But if you see my code again! I am updating lowerBound of ForEach and with this action I am breaking my agreement about constant Range! So SwiftUI comes and tell us if you are going update my ForEach range in count or any thing then you have to use an id
then you can update the given range! And the reason is because if we have 2 same item with same value, SwiftUI would have issue to know which one you say! with using an id
we are solving the identification issue for SwiftUI! About id you can use it like this: id:\.self
or like this id:\.customID
if your struct conform to Hash-able protocol, or in last case you can stop using id if you confrom your struct to identifiable protocol! then ForEach would magically sink itself with that.
Now see the edited code, it will build and run because we solved the issue of identification:
struct ContentView: View {
@State private var lowerBound: Int = 0
var body: some View {
ForEach(lowerBound..<11, id:\.self) { index in
Text(String(describing: index))
}
Button("update") { lowerBound = 5 }.padding()
}
}
Cannot get the @state variable in a ForEach loop working with SwiftUI
import SwiftUI
struct ContentView: View {
@State private var mainPrice = ""
@State private var mainGrade = ""
var body: some View {
let inputFields = [BindingWrapper(binding: $mainPrice), BindingWrapper(binding: $mainGrade)]
HStack {
List{
ForEach(inputFields, id: \.uuid) { value in /// here is the error
TextField("Enter data", text: value.$binding)
}
}
}
}
}
struct BindingWrapper {
let uuid: UUID = UUID()
@Binding var binding: String
}
Update @State variable (Int Counter) through ForEach button not possible?
Note that both Arrays and CharacterSelection are Value types, not Reference types. If you don't know the difference, check this page: https://developer.apple.com/swift/blog/?id=10
For your code to work, you can rewrite it like this:
struct ContentView : View {
@State var charactersList = [
CharacterSelection(id: 0, name: "Witch", count: 0),
CharacterSelection(id: 1, name: "Seer", count: 1),
CharacterSelection(id: 2, name: "Hunter", count: 0),
CharacterSelection(id: 3, name: "Knight", count: 0)
]
var body: some View {
VStack(alignment:.leading) {
ForEach(0..<charactersList.count) { i in
HStack{
Text(self.charactersList[i].name)
Spacer()
Text("\(self.charactersList[i].count)")
Button(action: { self.charactersList[i].count += 1 }) {
Text("Button")
}
}.padding(10)
}
Button(action: {
self.charactersList.append(CharacterSelection(id: self.charactersList.count, name: "something", count: 0))
}, label: {
Text("Add CharacterSelection")
})
}
}
}
SwiftUI How to Have a ForEach loop iterate based on how many days of a selected month in a Picker is
As far as I can tell you should be able to call your getRange(year:, month:) function in your ForEach. Passing in yearIndex
and monthIndex
.
I wrote up this little sample code to quickly test the theory. Let me know if this is what you're looking for.
-Dan
struct ContentView: View {
@State private var year = 3
@State private var month = 2
var body: some View {
List {
ForEach(0 ..< getRange(year: year, month: month)) { i in
Text("\(i)")
}
}
}
func getRange(year: Int, month: Int) -> Int {
return Calendar.current.range(of: .day, in: .month, for: Calendar.current.date(from: DateComponents(year: year, month: month + 1))!)!.count
}
}
edit to address the question in your comment;
To update your Picker label properly try changing your code to this:
Picker("Years", selection: $year) {
ForEach(0 ..< year) { index in
Text("\(index)")
}
}
See if that gives you the desired response. It's not what you're doing is entirely wrong, I'm just not super clear on what your ultimate intent is. But play around with the code, I think you're on the right track.
Related Topics
Programmatically Creating Constraints Bound to View Controller Margins
How Set Rootviewcontroller in Scene Delegate iOS 13
Implementing Nscopying in Swift with Subclasses
Grab Frames from Video Using Swift
Checking If Textfields Are Empty Swift
"Cannot Assign Value of Type 'String' to Type 'Anyobject'", Swift 3, Xcode 8 Beta 6
How to Change the Associated Values of a Enum
In Swiftui, Where Are the Control Events, I.E. Scrollviewdidscroll to Detect the Bottom of List Data
Get Image from Calayer or Nsview (Swift 3)
Background Request Not Execute Alamofire Swift
Back Button Image - What Is It Called in Swift
Two Tables on One View in Swift
iOS 8 Beta Today Extension Widget Not Showing in a Swift App
How to Limit the Movement of Two Anchored Lines So They Swing Continually Like a Pendulum
Converting Swift Array to Cfarray in Xcode 8 (Swift 3)
Get Build Date and Time in Swift