Can't create a range in Swift 3
In Swift 3, "Collections move their index", see
A New Model for Collections and Indices on Swift evolution.
Here is an example for String ranges and indices:
let string = "ABCDEFG"
if let range = string.range(of: "CDEF") {
let lo = string.index(range.lowerBound, offsetBy: 1)
let hi = string.index(range.lowerBound, offsetBy: 3)
let subRange = lo ..< hi
print(string[subRange]) // "DE"
}
The
public func index(_ i: Index, offsetBy n: IndexDistance) -> Index
method is called on the string to calculate the new indices from the
range (which has properties lower/upperBound
now instead ofstart/endIndex
).
How to create range in Swift?
Updated for Swift 4
Swift ranges are more complex than NSRange
, and they didn't get any easier in Swift 3. If you want to try to understand the reasoning behind some of this complexity, read this and this. I'll just show you how to create them and when you might use them.
Closed Ranges: a...b
This range operator creates a Swift range which includes both element a
and element b
, even if b
is the maximum possible value for a type (like Int.max
). There are two different types of closed ranges: ClosedRange
and CountableClosedRange
.
1. ClosedRange
The elements of all ranges in Swift are comparable (ie, they conform to the Comparable protocol). That allows you to access the elements in the range from a collection. Here is an example:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
However, a ClosedRange
is not countable (ie, it does not conform to the Sequence protocol). That means you can't iterate over the elements with a for
loop. For that you need the CountableClosedRange
.
2. CountableClosedRange
This is similar to the last one except now the range can also be iterated over.
let myRange: CountableClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
for index in myRange {
print(myArray[index])
}
Half-Open Ranges: a..<b
This range operator includes element a
but not element b
. Like above, there are two different types of half-open ranges: Range
and CountableRange
.
1. Range
As with ClosedRange
, you can access the elements of a collection with a Range
. Example:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Again, though, you cannot iterate over a Range
because it is only comparable, not stridable.
2. CountableRange
A CountableRange
allows iteration.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
NSRange
You can (must) still use NSRange
at times in Swift (when making attributed strings, for example), so it is helpful to know how to make one.
let myNSRange = NSRange(location: 3, length: 2)
Note that this is location and length, not start index and end index. The example here is similar in meaning to the Swift range 3..<5
. However, since the types are different, they are not interchangeable.
Ranges with Strings
The ...
and ..<
range operators are a shorthand way of creating ranges. For example:
let myRange = 1..<3
The long hand way to create the same range would be
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
You can see that the index type here is Int
. That doesn't work for String
, though, because Strings are made of Characters and not all characters are the same size. (Read this for more info.) An emoji like , for example, takes more space than the letter "b".
Problem with NSRange
Try experimenting with NSRange
and an NSString
with emoji and you'll see what I mean. Headache.
let myNSRange = NSRange(location: 1, length: 3)
let myNSString: NSString = "abcde"
myNSString.substring(with: myNSRange) // "bcd"
let myNSString2: NSString = "acde"
myNSString2.substring(with: myNSRange) // "c" Where is the "d"!?
The smiley face takes two UTF-16 code units to store, so it gives the unexpected result of not including the "d".
Swift Solution
Because of this, with Swift Strings you use Range<String.Index>
, not Range<Int>
. The String Index is calculated based on a particular string so that it knows if there are any emoji or extended grapheme clusters.
Example
var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString[myRange] // "bcd"
myString = "acde"
let start2 = myString.index(myString.startIndex, offsetBy: 1)
let end2 = myString.index(myString.startIndex, offsetBy: 4)
let myRange2 = start2..<end2
myString[myRange2] // "cd"
One-sided Ranges: a...
and ...b
and ..<b
In Swift 4 things were simplified a bit. Whenever the starting or ending point of a range can be inferred, you can leave it off.
Int
You can use one-sided integer ranges to iterate over collections. Here are some examples from the documentation.
// iterate from index 2 to the end of the array
for name in names[2...] {
print(name)
}
// iterate from the beginning of the array to index 2
for name in names[...2] {
print(name)
}
// iterate from the beginning of the array up to but not including index 2
for name in names[..<2] {
print(name)
}
// the range from negative infinity to 5. You can't iterate forward
// over this because the starting point in unknown.
let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true
// You can iterate over this but it will be an infinate loop
// so you have to break out at some point.
let range = 5...
String
This also works with String ranges. If you are making a range with str.startIndex
or str.endIndex
at one end, you can leave it off. The compiler will infer it.
Given
var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)
let myRange = ..<index // Hello
You can go from the index to str.endIndex by using ...
var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index... // playground
See also:
- How does String.Index work in Swift
- How does String substring work in Swift
Notes
- You can't use a range you created with one string on a different string.
- As you can see, String ranges are a pain in Swift, but they do make it possibly to deal better with emoji and other Unicode scalars.
Further Study
- String Range examples
- Range Operator documentation
- Strings and Characters documentation
Can't form Range with end start
Your for has two problems:
- If
slashList.count
is 0 (becauseslashList
is empty), it would try to count from 1 to 0 adding 1 (which results in an infinite loop), that's why the compiler gives you the errorstart > end
. - if
slashList.count
is greater than 0 (slashList
is not empty), it would use an index which is out of bounds, because you count from 1 toslashList.count
, while the indexes go from 0 toslashList.count - 1
to check all indexes it should be:
for i in 0 ..< slashList.count {
// your code
}
to ignore the first element (index 0) do:
for i in 1 ..< slashList.count {
// your code
}
for your special case, it would seem better to me to do something like:
for element in slashList {
if !quotaList.contains(element+1)
{
slashList.removeObject(element)
}
}
You can use removeObject from this answer. If, for some reason, you also need the index, do:
for (index, element) in slashList.enumerate() {
// your code
}
Swift fatal error: Can't form Range with end start
for in (0...row-1).reverse()
Swift can't read row-1...0
Swift 3 Ranges: Best Practices
It's actually pretty simple:
A Closed...Range
is produced by using three dots: 0...10
. This includes lower bound and upper bound. The opposite is a non-closed range, produced by 0..<10
which doesn't include the upper bound.
A Countable...Range
is a range of a type you can stride through with a signed integer, it is produced by either 0...10
or 0..<10
. These types conform to the Sequence
protocol.
A few examples:
0..<10 // CountableRange
0...Int.max // CountableClosedRange (this was not possible before Swift 3)
"A"..<"A" // Range, empty
"A"..."Z" // ClosedRange, because cannot stride through, only check if a character is in the bounds
You should probably make your methods accept a generic Collection
/Sequence
depending on what you need:
func test<C: Collection where C.Iterator.Element == Int>(s: C) {
print(s.first)
}
Maybe you can show one of your uses for Range<Int>
Can't form Range with end start Check range before doing for loop?
If you just want to iterate over a collection, then use the for <element> in <collection>
syntax.
for element in arr {
// do something with element
}
If you also need access to the index of the element at each iteration, you can use enumerate()
. Because indices are zero based, the index will have the range 0..<arr.count
.
for (index, element) in arr.enumerate() {
// do something with index & element
// if you need the position of the element (1st, 2nd 3rd etc), then do index+1
let position = index+1
}
You can always add one to the index at each iteration in order to access the position (to get a range of 1..<arr.count+1
).
If none of these solve your problem, then you can use the range 0..<arr.count
to iterate over the indices of your array, or as @vacawama says, you could use the range 1..<arr.count+1
to iterate over the positions.
for index in 0..<arr.count {
// do something with index
}
for position in 1..<arr.count+1 {
// do something with position
}
0..<0
cannot crash for an empty array as 0..<0
is just an empty range, and 1..<arr.count+1
cannot crash for an empty array as 1..<1
is also an empty range.
Also see @vacawama's comment below about using stride
for safely doing more custom ranges. For example (Swift 2 syntax):
let startIndex = 4
for i in startIndex.stride(to: arr.count, by: 1) {
// i = 4, 5, 6, 7 .. arr.count-1
}
Swift 3 syntax:
for i in stride(from: 4, to: arr.count, by: 1) {
// i = 4, 5, 6, 7 .. arr.count-1
}
This is where startIndex
is the number to start the range at, arr.count
is the number that the range will stay below, and 1
is the stride length. If your array has less elements than the given starting index, then the loop will never be entered.
Swift 3: Filter a range
You have to use a countable range:
let range: CountableRange<Int> = 1..<100
// Or simply: let range = 1..<100
let mult4 = range.filter { n in n % 4 == 0 }
A (Closed)Range
describes an "interval" and can not be enumerated,
whereas a Countable(Closed)Range
is a collection of consecutive values.
How to use range.map in swift 3/4?
Range
does not adopt Sequence
, just create the range literally as CountableClosedRange
let range = Int(min/grainSize)...Int(max/grainSize)
inout String is not convertible to String in Swift 3 range
In Swift 3, indices
property has become a Collection of Index, and not any more a sort of Range. If you want a Range of Index representing the whole String, you need to write something like this:
let myModifiedString = myOriginalString.replacingOccurrences(of: "#(?:\\S+)\\s?",
with: "",
options: .regularExpression,
range: myOriginalString.startIndex..<myOriginalString.endIndex)
Related Topics
Converting a C-Style for Loop That Uses Division for the Step to Swift 3
Know When an Iteration Over Array with Async Method Is Finished
Is This a Good Way to Display Asynchronous Data
How to Write a Function That Will Unwrap a Generic Property in Swift Assuming It Is an Optional Type
Convert Generic Free Function into Array Extension
How to Install Xcode on an External Hard Drive Along with the iPhone Simulator.App
How to Make the Memberwise Initialiser Public, by Default, for Structs in Swift
Swift Error: Missing Return in a Function Expected to Return 'String'
Autolayout Contraints for a View from Xib
Skphysicsbody Avoid Collision Swift/Spritekit
Uitextfield Setting Maximum Character Length in Swift
Center Item Inside Horizontal Stack
Why am I Allowed Method Access Less Restrictive Than Class Access
How to Integrate Uiactivityviewcontroller with Swiftui's Scrollview
When and How to Use @Noreturn Attribute in Swift