Reputation: 540
I'm trying to use a ForEach
to build the options of a picker in SwiftUI, but the range operator doesn't seem to be working as I would expect it to.
Here is my code:
struct ContentView: View {
@State private var inputString = ""
@State private var inputUnits = 0
let units = ["Fahrenheit", "Celsius", "Kelvin"]
var body: some View {
Form {
Section(header: Text("Convert:")) {
TextField("Enter input", text: $inputString)
Picker("Unit", selection: $inputUnits) {
ForEach(0 ... 2) {
Text("\(self.units[$0])")
}
}
}
}
}
}
The compiler has an issue with the ...
range operator. It throws two errors:
The curious thing here is that the ..<
operator works fine here, so if I use this line of code instead, the code compiles:
ForEach(0 ..< 3) {
This seems like a bug to me, but is there some difference between these operators that I am not aware of?
Upvotes: 4
Views: 1312
Reputation: 257749
Yes, there is a difference:
For Range<Int>
(..<
) operator there is explicit extension of ForEach
which specifies associated types
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension ForEach where Data == Range<Int>, ID == Int, Content : View {
whereas for ClosedRange<Int>
(...
) there is no such, and it is considered as collection by more generic
/// A structure that computes views on demand from an underlying collection of /// of identified data. @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) public struct ForEach<Data, ID, Content> where Data : RandomAccessCollection, ID : Hashable {
so requires explicit providing of ID type as below
ForEach(0...2, id: \.self) {
or you can declare your own extension
extension ForEach where Data == ClosedRange<Int>, ID == Int, Content : View {
public init(_ data: ClosedRange<Int>, @ViewBuilder content: @escaping (Int) -> Content) {
self.init(data, id: \.self, content: content)
}
}
and then just use
ForEach(0...2) {
Upvotes: 5
Reputation: 165
Solution to implement ForEach with Range operators is as follows:-
For Array of count 3
First with Closed Range is
ForEach(0...2, id: \.self){ index in
Text("\(self.array[index])")
}
Second is
ForEach(0..<3, id: \.self){ index in
Text("\(self.array[index])")
}
Upvotes: -1
Reputation: 2014
This is because 0 ... 2
is a ClosedRange<Int>
and 0 ..< 3
is a Range<Int>
.
If you look at the init
functions of ForEach
here you will see that there is this: init(Range<Int>, content: (Int) -> Content)
which matches the use of your 0 ..< 3
.
In many other places in swift you can mix ...
and ..<
because the function is either overloaded or it has a Collection
can use generics with the RangeExpression
protocol. In this case since ForEach
doesn't have a Collection
backing it other than your input so an overload is the only reasonable option.
This can be accomplished like this:
extension ForEach where Data == ClosedRange<Int>, ID == Int, Content: View {
public init(_ data: ClosedRange<Int>, @ViewBuilder content: @escaping (Int) -> Content) {
self.init(data, id: \.self, content: content)
}
}
Upvotes: 3