Steven Schveighoffer
Steven Schveighoffer

Reputation: 570

Equivalent for loop complex condition

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

Answers (6)

Doug Mead
Doug Mead

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

Sulthan
Sulthan

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

Oleg Danu
Oleg Danu

Reputation: 4159

I would also suggest more swift style one:

for index in 0..<gridSpacing.width * scale {
    // Your code here
}

Upvotes: 0

GetSwifty
GetSwifty

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

Doug Mead
Doug Mead

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

JAL
JAL

Reputation: 42459

Change it to a while loop?

var i: CGFloat = 0
while ((i * gridSpacing.width * scale) < bounds.width) {
    // ...
    i += 1;
}

Upvotes: 0

Related Questions