Reputation: 570
I have code currently in my iOS project that looks like this:
for var i = 0; CGFloat(i) * gridSpacing.width * scale < bounds.width; ++i
I am now receiving warnings that this style for loop will be deprecated. Everywhere I look on Stack Overflow and elsewhere suggests using stride for complex for loops. However, stride doesn't help me here, as the end condition is not a simple comparison to another integer.
How do I do this in swift with a for-in loop?
Note, I could loop a CGFloat, but I want to avoid the incremental error by repeatedly adding floats.
I could also use a while loop, but I have several of these loops in the same function, and for creates a nice new scope for i. Swift doesn't seem to like arbitrary new scopes.
Edit: based on @Sulthan's answer, I created a SequenceType to do this. IMO, something like this should be in swift if for loops are being removed:
struct forgenerator : GeneratorType
{
typealias Element = Int
typealias Condition = (Element) -> Bool
var idx : Element
var condition : Condition
var increment: Element
mutating func next() -> Element? {
if condition(idx)
{
let result = idx
idx += increment
return result
}
return nil
}
}
struct forsequence : SequenceType
{
typealias Generator = forgenerator
var startPoint : Generator.Element
var condition : Generator.Condition
var increment : Generator.Element
func generate() -> Generator {
return forgenerator(idx: startPoint, condition: condition, increment: increment)
}
}
func forgen(start: Int, condition: (Int) -> Bool, increment: Int) -> forsequence
{
return forsequence(startPoint: start, condition: condition, increment: increment)
}
func forgen(start: Int, condition: (Int) -> Bool) -> forsequence
{
return forgen(start, condition: condition, increment: 1)
}
Usage:
for i in forgen(0, condition: { CGFloat($0) * self.gridSpacing.width * self.scale < self.bounds.width})
Note, had to use self here everywhere because all those vars were members of a class.
Upvotes: 0
Views: 408
Reputation: 942
I just discovered that for-in loops have a where
clause!!
An example of the usage:
var end: CGFloat = bounds.width
for i in 0..<(Int(end+1)) where CGFloat(i) * gridSpacing.width * scale < CGFloat(end) {
print(i)
}
Upvotes: 0
Reputation: 130132
Let's go in steps:
let itemWidth = gridSpacing.width * scale
for var i = 0; CGFloat(i) < bounds.width / itemWidth; ++i {
}
Now you can directly
let itemWidth = gridSpacing.width * scale
let numItems = Int(floor(Double(bounds.width / itemWidth)))
for var i = 0; i < numItems; i++ {
}
or
for i in 0..<numItems {
}
Another option is to use while
loop:
(I have used the solution you want to avoid but I feel it's still better and it's unlikely the error will be more important than the error introduced by scale
).
let itemWidth = gridSpacing.width * scale
var x = 0
var index = 0
while (x < bounds.width) {
// do something
index += 1
x += itemWidth
}
Also, you can always extract the iteration into a function:
func strideOverFloats(start: Int, addition: Int, condition: (CGFloat) -> Bool, _ closure: (Int) -> ()) {
var i = start
while (condition(CGFloat(i))) {
closure(i)
i += addition
}
}
let width: CGFloat = 200
let gridSize: CGFloat = 10
let scale: CGFloat = 2
strideOverFloats(0, addition: 1, condition: { $0 * gridSize * scale < width}) {
print($0)
}
Upvotes: 0
Reputation: 4159
I would also suggest more swift style one:
for index in 0..<gridSpacing.width * scale {
// Your code here
}
Upvotes: 0
Reputation: 7756
There's always recursion! Avoids while loops while still allowing you to deal with changing constraints. And it's functional! ;-)
func recursion(currentIndex: Int) {
guard CGFloat(currentIndex) * gridSpacing.width * scale < bounds.width else {
return
}
// do what you need to
recursion(currentIndex + 1)
}
Upvotes: 1
Reputation: 942
You could accomplish this with a while loop:
var i: Int = 0
while CGFloat(i) * a * b < bounds.width {
// Your code
i += 1 // ++ is also deprecated
}
I think their intention is that for-in
is used when the number of times being executed is known and while
when it's conditional on variables that can be modified within the loop.
You could also use a conditional break statement within the for-in
but the while
seems cleaner.
Upvotes: 0
Reputation: 42459
Change it to a while
loop?
var i: CGFloat = 0
while ((i * gridSpacing.width * scale) < bounds.width) {
// ...
i += 1;
}
Upvotes: 0