Find an item and change value in custom object array - Swift
Asked Answered
S

5

87

I have this class

class InboxInterests {

    var title = ""
    var eventID = 0
    var count = ""
    var added = 0

    init(title : String, eventID : NSInteger, count: String, added : NSInteger) {
        self.title = title
        self.eventID = eventID
        self.count = count
        self.added = added

    }
}

And i use it like this

var array: [InboxInterests] = [InboxInterests]()

Add item

let post = InboxInterests(title: "test",eventID : 1, count: "test", added: 0)
self.array.append(post)

I want to find the index by eventID key and change the value of added key in the same index

How is that possible?

Suchlike answered 28/6, 2016 at 19:9 Comment(0)
A
85

Since you are using a class, use filter and first to find the value:

array.filter({$0.eventID == id}).first?.added = value

In this you:

  1. filter the array down to elements that match the event ID
  2. pick the first result, if any
  3. then set the value

This works since classes are pass by reference. When you edit the return value from array.filter({$0.eventID == id}).first?, you edit the underlying value. You'll need to see the answers below if you are using a struct

EDIT: In Swift 3 you can save yourself a couple of characters

array.first({$0.eventID == id})?.added = value

EDIT: Swift 4.2:

array.first(where: { $0.eventID == id })?.added = value
array.filter {$0.eventID == id}.first?.added = value
Actino answered 28/6, 2016 at 19:13 Comment(7)
Or to do all of the items with that eventID: array.filter({$0.eventID == id}).forEach { $0.added = value }Handbill
am i the only person whose values in array are not update by this method :( ;(Hap
Not working for me also. Getting array cannot assign value, $0 is immutable.Homochromatic
If your values are not updating, probably you are using Struct instead of Class.Chitchat
@FarhanArshad Im using struct. so how should I do this instead?Burkhard
@Burkhard Use "class" instead of "struct".Chitchat
@FarhanArshad yep. I did use struct. now im using class.Burkhard
D
194

For me, the above answer did not work. So, what I did was first find the index of the object that I want to replace then using the index replace it with the new value

if let row = self.upcoming.index(where: {$0.eventID == id}) {
       array[row] = newValue
}

In Swift 5.0:

if let row = self.upcoming.firstIndex(where: {$0.eventID == id}) {
       array[row] = newValue
}
Dyane answered 31/10, 2017 at 20:59 Comment(3)
This is the perfect way to get the index and to replace the object as well.Chelyuskin
For me the accepted answer didn't work either and only your answer did. Very helpful!Cochabamba
What is upcoming?Zelikow
A
85

Since you are using a class, use filter and first to find the value:

array.filter({$0.eventID == id}).first?.added = value

In this you:

  1. filter the array down to elements that match the event ID
  2. pick the first result, if any
  3. then set the value

This works since classes are pass by reference. When you edit the return value from array.filter({$0.eventID == id}).first?, you edit the underlying value. You'll need to see the answers below if you are using a struct

EDIT: In Swift 3 you can save yourself a couple of characters

array.first({$0.eventID == id})?.added = value

EDIT: Swift 4.2:

array.first(where: { $0.eventID == id })?.added = value
array.filter {$0.eventID == id}.first?.added = value
Actino answered 28/6, 2016 at 19:13 Comment(7)
Or to do all of the items with that eventID: array.filter({$0.eventID == id}).forEach { $0.added = value }Handbill
am i the only person whose values in array are not update by this method :( ;(Hap
Not working for me also. Getting array cannot assign value, $0 is immutable.Homochromatic
If your values are not updating, probably you are using Struct instead of Class.Chitchat
@FarhanArshad Im using struct. so how should I do this instead?Burkhard
@Burkhard Use "class" instead of "struct".Chitchat
@FarhanArshad yep. I did use struct. now im using class.Burkhard
S
34

The filter operator is not the best in this case, it works for some of you because classes are passed by reference.

Explanation: (You can copy the following code in a playground if you want to verify it).

class Book {
    let id: Int
    var title = "default"

    init (id: Int) {
        self.id = id
    }
}
var arrayBook = [Book]()
arrayBook.append(Book(id: 0))
arrayBook.append(Book(id:1))
arrayBook.forEach { book in
    print(book.title)
}

arrayBook.filter{ $0.id == 1 }.first?.title = "modified"

arrayBook.forEach { book in
    print(book.title)
}

Arrays are copied by value not reference, so when you are using filter you are creating a new array (different than the initial), but when you modify the new one, the initial one gets modified too because both are pointing to the same class (classed are passed by reference), so after the filter your array will have changed and the new one gets deallocated. So in this case it will print "default", "default" and then "default, "modified".

What happens if you change class for struct, the value will be passed by value not reference so you will have 2 arrays in memory with different values, so if you go through arrayBooks again it will print before the filter "default","default", and then "default", "default" again. Because when you are using the filter you are creating and modifying a new array that will get deallocated if you do not store it).

The solution is using map, creating a new array with all the values but with the modified items or fields that we want and then replace our array with the new one. This will print "default", "default" before the map, and then "default", "modified"

This will work with structs, classes and everything that you want :).

struct Book {
    let id: Int
    var title = "default"

    init (id: Int) {
        self.id = id
    }
}
var arrayBook = [Book]()
arrayBook.append(Book(id: 0))
arrayBook.append(Book(id:1))
arrayBook.forEach { book in
    print(book.title)
}

arrayBook = arrayBook.map{
    var mutableBook = $0
    if $0.id == 1 {
        mutableBook.title = "modified"
    }
    return mutableBook
}

arrayBook.forEach { book in
    print(book.title)
}
Shericesheridan answered 27/3, 2018 at 13:31 Comment(1)
I'm getting same result default default default modified for both on Swift 4.2Scottiescottish
U
9
array = array.map { $0.eventID == id ? newValue : $0 }
Unconditioned answered 18/5, 2021 at 16:46 Comment(3)
While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value.Forwhy
This answer is good, but there is no explanation of how it works despite the previous comment asking for one, so I thought I would add one below:Cyclohexane
The map function creates a new array by applying the closure to each element in the old array. array (which is the old array) will be replaced with the new array returned from the map. The closure uses the ternary operator which is the same as an if-then-else statement. $0 simply represents each element of array as the map function iterates through. So what this does is iterate through array and check each element to see if eventID == id. If it does then that element is set equal to newValue otherwise it is set equal to itself, so that only the element with the correct id is updated.Cyclohexane
D
4

If you conform your class to Equatable then this would work:

extension Array where Element: Equatable {
    @discardableResult
    public mutating func replace(_ element: Element, with new: Element) -> Bool {
        if let f = self.firstIndex(where: { $0 == element}) {
            self[f] = new
            return true
        }
        return false
    }
}

Use like this:

array.replace(prev, with: new)
Doubtless answered 11/5, 2021 at 16:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.