Cannot write to array inside while loop - Subscript is get only - swift

I am having the error -
Cannot assign through subscript: subscript is read only
This occurs when trying to write elements of an array inside a while loop as can be seen in code below. I understand this could be caused by swift mutability however my understanding of this is poor/nil and I'm struggling to find documentation.
Code -
while (counter != ((localArray.count) + 1))
{
data[counter] = /// The error flags on this line for the data array
[
"type" : localArray[counter][0],
"details" : localArray[counter][1],
"cost" : localArray[counter][2]
]
counter = counter + 1
}
EDIT for D4ttatraya -
'data' is declared as
var data: [[String: String]]?
and localArray is a copy of the result of a function -
var result: [[String]] = []
let localArray = result

First instead of declaring type of data array, create empty data array
var data = [[String: String]]()
Second I would suggest you to replace while loop with for-each loop for each inner array inside localArray (then instead of using counter variable you can use elements inside each array).
for array in localArray {
}
Now in loop you can just append new element to data array
data.append(["type": array[0],
"details": array[1],
"cost": array[2]])
also at the beginning of the loop make sure that each array has three elements inside, if hasn't continue to other inner array
guard array.count == 3 else { continue }
So the whole for-each loop should look like this:
for array in localArray {
guard array.count == 3 else { continue }
data.append(["type": array[0],
"details": array[1],
"cost": array[2]])
}

You data is optional so you need to create it before you can add stuff to it, also you are enumerating through localArray in order and then just adding to the end of data so you can do something like this
var data = [[String: String]]();
for theLocal in localArray {
data.append([
"type" : theLocal[0],
"details" : theLocal[1],
"cost" : theLocal[2]
]);
}

data is an optional, but you're trying to access it as a non-optional value. Try data?[counter] or if var data = data { data[counter] = ... }

Related

Filter An Array Of Arrays By A Value In Firestore Swift

I'm looking to filter an array of arrays by specific value of one of the keys located within each array. Each nested array is read in from Firestore.
As an object, each nested array would look like this:
struct Video {
var url: String
var submissionDate: Timestamp
var submittingUser: String
}
I'm reading it in like this:
videos = document.get("videos") as? [[String : Any]] ?? nil
So far so good, but when I filter it like this:
filteredVideos = videos.filter { $0[2].contains(self.userIdentification) }
I can't do it without getting the error "Reference to member 'contains' cannot be resolved without a contextual type," an error which I was unable to find any relevant information on SO about.
I have read that some people say "Don't use arrays in Firestore!" but this is a build requirement.
Anyone have any ideas? Basically just need all arrays within the array where userId == submittingUser.
Reference Article:
I tried the answer from here: How to filter out a Array of Array's but no luck for this situation.
It's actually an array of dictionaries, not an array of arrays. All you need to do is construct the right predicate for the filter.
This is basically what your Firestore data looks like:
let videos: [[String: Any]] = [
["url": "http://www...", "submittingUser": "user1"],
["url": "http://www...", "submittingUser": "user2"]
]
This is the user you're looking for:
let userIdentification = "user2"
This is the predicate for the filer:
let filteredVideos = videos.filter { (video) -> Bool in
if let submittingUser = video["submittingUser"] as? String,
submittingUser == userIdentification {
return true
} else {
return false
}
}
You can shorthand this down to a single line if you're okay with force-unwrapping the dictionary (if you're 100% certain every video will have a valid submittingUser value):
let filteredVideos = videos.filter({ $0["submittingUser"] as! String == userIdentification })

How to update field values in a document - Firebase

I have a collection called objects / documents of type Object that has an an array of type ObjectNotification, I am trying to update each notification.read to true.
I have a view that displays all the userNotifications from all objects in a single array self.viewModel.userNotifications.
When onAppear for the view I am trying to set each userNotification.read to true in self.viewModel.userNotifications and update the FirestoreDB.
However I am not sure the best approach to take, currently I am looping through the arrays and trying to update each userNotification in self.viewModel.objects.userNotifications then update the document in the DB, which will update self.viewModel.userNotifications as that fetches all self.viewModel.userNotifications.
But I get the following error as I am trying to change a struct, I was trying to change the value in my for in statement then called my updateObect(object) method to update the document in the DB.
Cannot assign through subscript: 'h' is a 'let' constant
.onAppear() {
// Mark as read
let read = objectManager.markNotificationsAsRead(self.viewModel.userNotifications)
self.viewModel.updateObjectNotifications(readNotifcations: read)
}
func markNotificationsAsRead(_ notifications: [ObjectNotification]) -> [ObjectNotification]{
// Mark notifications as read
var readNotifications: [ObjectNotification] = []
for n in notifications {
if n.read == false {
// Create new with true
var new = n
new.read = true
readNotifications.append(new)
}
}
// Return update notifications with read = true
return readNotifications
}
func updateObjectNotifications(readNotifcations: [ObjectNotification]) {
if let currentUser = Auth.auth().currentUser {
for h in self.objects {
for n in h.notifications {
if n.deliveredTo == currentUser.email {
for r in readNotifcations {
if r.id == n.id {
// same notif for home
// h.notifications.rem
if let index = h.notifications.firstIndex(of: n) {
h.notifications[index] = r // Cannot assign through subscript: 'h' is a 'let' constant
}
}
}
}
// update object in db
}
}
}
}
Instead of the approach above, how can I change the fields in the database ?
This is is a solution to the actual problem and does not address the Firebase part of the question as that was not outlined in the actual question.
I'm going to take a wild guess here as the question is incomplete but I think whatever object 'h' is in the question is a struct which contains an array property of notifications.
If so, then you can't do this within a for loop
h.notifications[index] = r
because Structs are value types, unlike classes that are reference types. That means within the for loop, the objects are a copy of the array element, not the element itself
for copyOfArrayElement in someArrayOfStructs {}
There are a few solutions; here's two. The first is to iterate over the array using an index to access the actual struct object. Suppose we have an array of fruit structs with a name and an array property
struct FruitStruct {
var name = ""
var someList = [String]()
}
var banana = FruitStruct(name: "banana", someList: ["a", "b", "c"])
var grape = FruitStruct(name: "grape", someList: ["d", "e", "f"])
var fruitsArray = [banana, grape]
then the loop to modify every fruit name to be 'Hello' and element with index of 1 within the someList to be 'World'
for i in 0..<fruitsArray.count {
fruitsArray[i].name = "Hello"
fruitsArray[i].someList[1] = "World"
}
fruitsArray.forEach { print($0.name, $0.someList) }
and the output
Hello ["a", "World", "c"]
Hello ["c", "World", "e"]
Alternately change the struct to a class (which is a reference) so you can then modify the properties directly using your existing loop.
class FruitClass {
var name = ""
var someList = [String]()
convenience init(withName: String, andArray: [String] ) {
self.init()
self.name = withName
self.someList = andArray
}
}

What is the best way in Swift 4+ to store a set of homogenous arrays for various types in a dictionary?

Consider a situation where we want to have a dictionary of arrays, with each array being a homogeneous collection of values of some type (which may be a struct or a primitive type). I'm currently using the ObjectIdentifier of the type defining it thusly:
let pInts : [UInt32] = [4, 6, 99, 1001, 2032]
let pFloats : [Float] = [3.14159, 8.9]
let pBools : [Bool] = [true, false, true]
let myDataStructure : [ObjectIdentifier : [Any]] = [
ObjectIdentifier(Float.self) : pFloats,
ObjectIdentifier(UInt32.self) : pInts,
ObjectIdentifier(Bool.self) : pBools
]
The issue here is that when traversing the data structure, Swift doesn't know that the objects in each list are homogeneous. Since swift is statically typed, I'm guessing it is not possible to typecast the [Any] lists using the ObjectIdentifier keys. Consider this traversal pseudocode:
for (typeObjId, listOfValuesOfSometype) in myDataStructure {
// do something like swap values around in the array,
// knowing they are homogeneously but anonymously typed
}
So, is there some metatype machinery I can concoct to represent this data structure in a way that does not anticipate the list of actual types that will have arrays in it?
I'm not exactly sure what you want to accomplish, Inside the dictionary loop the arrays will always be of type Any, but if you want to move items in the arrays around, you could just do that. Just reassign the array first to a var and then put it back in the dictionary.
If you do want to loop through the items of a specific type, then you could use the array helper function below.
func testX() {
let pInts: [UInt32] = [4, 6, 99, 1001, 2032]
let pFloats: [Float] = [3.14159, 8.9]
let pBools: [Bool] = [true, false, true]
var myDataStructure: [ObjectIdentifier: [Any]] = [
ObjectIdentifier(Float.self): pFloats,
ObjectIdentifier(UInt32.self): pInts,
ObjectIdentifier(Bool.self): pBools
]
// Swap the first 2 items of every array
for d in myDataStructure {
var i = d.value
if i.count > 1 {
let s = i[0]
i[0] = i[1]
i[1] = s
}
myDataStructure[d.key] = i
}
// Now dump all data per specific type using the array helper function.
for i: UInt32 in array(myDataStructure) {
print(i)
}
for i: Float in array(myDataStructure) {
print(i)
}
for i: Bool in array(myDataStructure) {
print(i)
}
}
func array<T>(_ data: [ObjectIdentifier: [Any]]) -> [T] {
return data[ObjectIdentifier(T.self)] as? [T] ?? []
}

How do I update Swift Dictionary values of mixed types, in particular Arrays

I have a dictionary of mixed types (string, NSImage, and an Array). When appending to the array, I get the error "Value of type 'Any??' has no member 'append'". I don't see how to cast "file_list" value as an Array so I can append values to it.
var dataDict: [String:Any?] = [
"data_id" : someString,
"thumbnail" : nil,
"file_list" : [],
]
// do stuff... find files... whirrr wizzzz
dataDict["thumbnail"] = NSImage(byReferencingFile: someFile)
dataDict["file_list"].append( someFile ) <- ERROR: Value of type 'Any??' has no member 'append'
You can't. You need to first get your key value, cast from Any to [String], append the new value and then assign the modified array value to your key:
if var array = dataDict["file_list"] as? [String] {
array.append(someFile)
dataDict["file_list"] = array
}
or
if let array = dataDict["file_list"] as? [String] {
dataDict["file_list"] = array + [someFile]
}
Another option is to create a custom struct as suggested in comments by OOPer

Most efficient way to flatten an array of dictionaries containing arrays

Let's say I have an array of dictionaries, and each contains an array of letters. Like this:
let dicts = [["letters" : ["a","b","c"]],
["letters" : ["d","e","f"]]]
What is the most efficient way to create a flattened array of all the letters from all the dictionaries?
You can use reduce(_:_:) for that.
let array = dicts.reduce([]) { $0 + ($1["letters"] ?? []) }
print(array) // ["a","b","c","d","e","f"]
Edit: As #Hamish suggested a link in comment simplest solution is having less time, so if you are having large amount of data then you can use forEach closure with array.
var result = [String]()
dicts.forEach {
result.append(contentsOf: $0["letters"] ?? [])
}
You can use flatMap() to map each dictionary to the corresponding
letters array and join the results:
let dicts = [["letters" : ["a","b","c"]],
["letters" : ["d","e","f"]]]
let letters = Array(dicts.flatMap { $0["letters"] }.joined())
print(letters) // ["a", "b", "c", "d", "e", "f"]
This is effective insofar as no additional intermediate arrays are
created during the join. As #Hamish pointed out below, the
intermediate [[String]] can be avoided with
let letters = Array(dicts.lazy.flatMap { $0["letters"] }.joined())
You can use any one of these. You don't need to specify the key name of the dictionary.
Solution 1
let array = dicts.reduce([]) { $0 + ($1.values.reduce([]) { $0 + $1 }) }
print(array) // ["a","b","c","d","e","f"]
Solution 2
let array = dicts.flatMap { $0.values.joined() }
print(array) // ["a","b","c","d","e","f"]