John
John

Reputation: 540

Why is the inclusive range operator throwing an error when the exclusive operator does not?

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:

  1. Generic parameter 'ID' could not be inferred
  2. Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'Int' conform to 'Identifiable'

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

Answers (3)

Asperi
Asperi

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

Komal Gupta
Komal Gupta

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

bscothern
bscothern

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

Related Questions