Caspert
Caspert

Reputation: 4363

How to loop through array and start again if the array is at the end?

I have an array of colors, that I want to apply to uitableviewcells in iOS.

let colorPalet = [
            UIColor(red: 255.0/255.0, green: 159.0/255.0, blue: 112.0/255.0, alpha: 1),
            UIColor(red: 81.0/255.0, green: 218.0/255.0, blue: 168.0/255.0, alpha: 1),
            UIColor(red: 2.0/255.0, green: 207.0/255.0, blue: 255.0/255.0, alpha: 1),
            UIColor(red: 144.0/255.0, green: 153.0/255.0, blue: 166.0/255.0, alpha: 1)
        ]
        cell.backgroundColor = colorPalet[indexPath.row]

The problem is, then when indexPath.row is greater then the colorPalet array, it will crash, because there is no more entries in the array. How to start iteratie again through the array if it is at the end of the array in Swift?

Upvotes: 0

Views: 1001

Answers (5)

dfrib
dfrib

Reputation: 73186

The accepted answer already covers the specific situation of the OP (cell colors in a table view), whereas this answer will approach the more general question title:

How to loop through array and start again if the array is at the end?

The modulo operation naturally comes to mind,

truncatedIndex = runningIndexDividend % divisor 

But what if we're to use this in a long running application, where the runningIndexDividend needlessly increase to values much larger than the divisor (possibly, in a theoretic contrived situation, even leading to integer overflow ...)? For such a case, an alternative, mainly to tap into the Sequence neatness of Swift, is to use an on-the-fly generating sequence: one which lazily constructs its next element upon demand.

Using the global sequence(state:next:) function

For the case of constructing a (infinite) sequence which repeatedly traverses a given array ("joining" the head with the tail), you could make use of the global sequence(state:next:) function.

E.g., applied to your example (here storing the colorPalet as a static member of ColorSettings utility class, just know the possible non-thread safety if using static properties in threaded applications):

class ColorSettings {
    private static let colorPalet = [
        UIColor(red: 255.0/255.0, green: 159.0/255.0, blue: 112.0/255.0, alpha: 1),
        UIColor(red: 81.0/255.0, green: 218.0/255.0, blue: 168.0/255.0, alpha: 1),
        UIColor(red: 2.0/255.0, green: 207.0/255.0, blue: 255.0/255.0, alpha: 1),
        UIColor(red: 144.0/255.0, green: 153.0/255.0, blue: 166.0/255.0, alpha: 1)
    ]

    static let colorSequence = sequence(
        state: 1,
        next: { (idx: inout Int) -> UIColor? in
            guard colorPalet.count > 0 else { return nil }
            defer { idx == colorPalet.count ? (idx = 1) : (idx += 1) }
            /* alternatively (loose clarity/semantics to gain brevity)
            defer { idx = idx % colorPalet.count + 1 } */
            return colorPalet[idx-1]
    })
}

Example "usage" (not really intended for this application)

// example usage
let numberOfRowsInSection = 7
for (row, color) in zip(0..<numberOfRowsInSection,
                        ColorSettings.colorSequence) {
    // ...
    print(row, color)
} /* 0 UIExtendedSRGBColorSpace 1 0.623529 0.439216 1
     1 UIExtendedSRGBColorSpace 0.317647 0.854902 0.658824 1
     2 UIExtendedSRGBColorSpace 0.00784314 0.811765 1 1
     3 UIExtendedSRGBColorSpace 0.564706 0.6 0.65098 1
     4 UIExtendedSRGBColorSpace 1 0.623529 0.439216 1
     5 UIExtendedSRGBColorSpace 0.317647 0.854902 0.658824 1
     6 UIExtendedSRGBColorSpace 0.00784314 0.811765 1 1        */

Note that the state will not be saved between two separate traversals of colorSequence. I.e., if copying the loop above and applying elsewhere, the first state will always correspond to the first color.

Also beware that when constructing an infinitely generating sequence as the one above, the sequence can naturally not terminate by itself (no nil return, apart from the empty colorPalet array case). Hence its practical use will mostly be in conjunction with a finite sequence with use of zip as above.

Using an external state property with AnyIterator

If you'd rather keep the end state in one traversal as a starting point for the subsequent one (not resetting it, as above), you could use an approach similar to the one above, but using a help state property combined with AnyIterator:

class ColorSettings {
    private static let colorPalet = [
        UIColor(red: 255.0/255.0, green: 159.0/255.0, blue: 112.0/255.0, alpha: 1),
        UIColor(red: 81.0/255.0, green: 218.0/255.0, blue: 168.0/255.0, alpha: 1),
        UIColor(red: 2.0/255.0, green: 207.0/255.0, blue: 255.0/255.0, alpha: 1),
        UIColor(red: 144.0/255.0, green: 153.0/255.0, blue: 166.0/255.0, alpha: 1)
    ]

    private static var idx: Int = 1
    static let colorIterator: AnyIterator<UIColor> = AnyIterator {
        guard colorPalet.count > 0 else { return nil }
        defer { idx == colorPalet.count ? (idx = 1) : (idx += 1) }
        return colorPalet[idx-1]
    }
}

Example usage:

// first traversal
for (i, color) in zip(0..<2, ColorSettings.colorIterator) {
    // ...
    print(i, color)
} /* 0 UIExtendedSRGBColorSpace 1 0.623529 0.439216 1
     1 UIExtendedSRGBColorSpace 0.317647 0.854902 0.658824 1  */

// state from previous traversal will be used
// to decide starting state here, in next
for (i, color) in zip(0..<4, ColorSettings.colorIterator) {
    // ...
    print(i, color)
} /* 0 UIExtendedSRGBColorSpace 0.00784314 0.811765 1 1
     1 UIExtendedSRGBColorSpace 0.564706 0.6 0.65098 1
     2 UIExtendedSRGBColorSpace 1 0.623529 0.439216 1
     3 UIExtendedSRGBColorSpace 0.317647 0.854902 0.658824 1   */

Upvotes: 0

Duncan C
Duncan C

Reputation: 131426

Make colorPalet an instance variable. You can just move the code below to the top of the class:

let colorPalet = [
            UIColor(red: 255.0/255.0, green: 159.0/255.0, blue: 112.0/255.0, alpha: 1),
            UIColor(red: 81.0/255.0, green: 218.0/255.0, blue: 168.0/255.0, alpha: 1),
            UIColor(red: 2.0/255.0, green: 207.0/255.0, blue: 255.0/255.0, alpha: 1),
            UIColor(red: 144.0/255.0, green: 153.0/255.0, blue: 166.0/255.0, alpha: 1)
        ]

That way you're not creating an array of colors for each cell you configure. Then use the modulo (`%) Code in ROC's answer:

cell.backgroudColor = colorPalet[indexPath.row % colorPalet.count]

Upvotes: 1

ROC
ROC

Reputation: 78

you can use modulo:

cell.backgroudColor = colorPalet[indexPath.row % colorPalet.count]

Upvotes: 5

Bhavuk Jain
Bhavuk Jain

Reputation: 2187

You can use something like this:

let index = indexPath.row%4
cell.something = colorPalet[index]

Upvotes: 0

Shayan Jalil
Shayan Jalil

Reputation: 588

for index in 0..array.count {

    //Do stuff here
    if (index == array.count -1)
    {
        index = 0
    }
}

Upvotes: 0

Related Questions