49

I am still not sure about the rules of struct copy or reference.

I want to mutate a struct object while iterating on it from an array: For instance in this case I would like to change the background color but the compiler is yelling at me

struct Options {
  var backgroundColor = UIColor.blackColor()
}

var arrayOfMyStruct = [MyStruct]

...

for obj in arrayOfMyStruct {
  obj.backgroundColor = UIColor.redColor() // ! get an error
}
Avba
  • 14,822
  • 20
  • 92
  • 192

7 Answers7

61

struct are value types, thus in the for loop you are dealing with a copy.

Just as a test you might try this:

Swift 3:

struct Options {
   var backgroundColor = UIColor.black
}

var arrayOfMyStruct = [Options]()

for (index, _) in arrayOfMyStruct.enumerated() {
   arrayOfMyStruct[index].backgroundColor = UIColor.red
}

Swift 2:

struct Options {
    var backgroundColor = UIColor.blackColor()
}

var arrayOfMyStruct = [Options]()

for (index, _) in enumerate(arrayOfMyStruct) {
    arrayOfMyStruct[index].backgroundColor = UIColor.redColor() 
}

Here you just enumerate the index, and access directly the value stored in the array.

Hope this helps.

Matteo Piombo
  • 6,688
  • 2
  • 25
  • 25
  • I have one clarifying question: If you had specified a name for the current `MyStruct` object and since the array is passed by value, trying to print out the named struct object's `backgroundColor` on the next line would result in the old value being printed, correct? – dudeman Apr 10 '16 at 22:29
  • 1
    This does not work for Swift 3, please see my answer below for Swift 3. – ldoogy Oct 13 '16 at 00:01
26

You can use use Array.indices:

for index in arrayOfMyStruct.indices {
    arrayOfMyStruct[index].backgroundColor = UIColor.red
}
Emil Laine
  • 41,598
  • 9
  • 101
  • 157
  • 11
    Beware of the discussion from Apple's docs: `A collection’s indices property can hold a strong reference to the collection itself, causing the collection to be nonuniquely referenced. If you mutate the collection while iterating over its indices, a strong reference can result in an unexpected copy of the collection. To avoid the unexpected copy, use the index(after:) method starting with startIndex to produce indices instead.` Source: [https://developer.apple.com/documentation/swift/collection/1641719-indices] – Mihai Erős Jun 29 '18 at 11:07
  • 3
    The link in @Mihai Erös comment was moved to: https://developer.apple.com/documentation/swift/string/2949967-indices – jvarela Aug 29 '19 at 10:22
  • 1
    @MihaiErős would this apply to `for (index, _) in arrayOfMyStruct.enumerated()` as well? – cumanzor Aug 03 '20 at 18:05
  • Correct link for the documentation page: https://developer.apple.com/documentation/swift/collection/1641719-indices – Ralf Ebert Apr 16 '21 at 19:57
  • The enumerated() docs do not mention anything about that. Also I'd still use .indices just for the sake of clarity; but good to know to pay attention if you're handling a very large list. – Ralf Ebert Apr 16 '21 at 19:59
13

You are working with struct objects which are copied to local variable when using for in loop. Also array is a struct object, so if you want to mutate all members of the array, you have to create modified copy of original array filled by modified copies of original objects.

arrayOfMyStructs = arrayOfMyStructs.map { el in
   var el = el
   el.backgroundColor = .red
   return el
}

It can be simplified by adding this Array extension.

Swift 4/5

extension Array {
    mutating func mutateEach(by transform: (inout Element) throws -> Void) rethrows {
        self = try map { el in
            var el = el
            try transform(&el)
            return el
        }
     }
}

Usage

arrayOfMyStructs.mutateEach { el in
    el.backgroundColor = .red
}
Ondrej Stocek
  • 2,102
  • 1
  • 18
  • 13
5

For Swift 3, use the enumerated() method.

For example:

for (index, _) in arrayOfMyStruct.enumerated() {
  arrayOfMyStruct[index].backgroundColor = UIColor.redColor() 
}

The tuple also includes a copy of the object, so you could use for (index, object) instead to get to the object directly, but since it's a copy you would not be able to mutate the array in this way, and should use the index to do so. To directly quote the documentation:

If you need the integer index of each item as well as its value, use the enumerated() method to iterate over the array instead. For each item in the array, the enumerated() method returns a tuple composed of an integer and the item.

charles
  • 11,212
  • 3
  • 31
  • 46
ldoogy
  • 2,819
  • 1
  • 24
  • 38
  • 1
    There's no need to use the enumerated method if you *only* need the indices. Just use `for i in 0..< arrayOfMyStruct.count` – Peter Schorn Jun 30 '20 at 13:33
1

Another way not to write subscript expression every time.

struct Options {
    var backgroundColor = UIColor.black
}

var arrayOfMyStruct = [Options(), Options(), Options()]

for index in arrayOfMyStruct.indices {
    var option: Options {
        get { arrayOfMyStruct[index] }
        set { arrayOfMyStruct[index] = newValue }
    }
    option.backgroundColor = .red
}
Artem Bobrov
  • 151
  • 2
  • 8
0

This is my sample code

arrayOfMyStruct.indices.forEach { arrayOfMyStruct[$0].backgroundColor=UIColor.red }
-8

I saw this method in some code and it seems to be working

for (var mutableStruct) in arrayOfMyStruct {
  mutableStruct.backgroundColor = UIColor.redColor()
}
Avba
  • 14,822
  • 20
  • 92
  • 192
  • 6
    That 'var' makes the item you are iterating over locally mutable (but does not change the backing struct). – Entalpi Mar 25 '16 at 22:03