first or filter function of swift array doesnt give the right reference object - swift

I have defined a struct in my viewcontroller before my viewdidload
struct CustomFilterButton {
var Id : Int = 0;
var Name : String = "";
var selected : Bool = false;
}
then I create reference for it in global
var customButtons = [CustomFilterButton]();
then in my viewdidload I appended some customFilterButton objects in customButtons array
customButtons.append(CustomFilterButton.init(Id: 1, Name: "A", selected: false))
customButtons.append(CustomFilterButton.init(Id: 2, Name: "B", selected: false))
customButtons.append(CustomFilterButton.init(Id: 3, Name: "C", selected: true))
customButtons.append(CustomFilterButton.init(Id: 4, Name: "D", selected: false))
in viewdidload or in any other function when I try to get an object in array using first or filter and change it, but it doesnt work.
print(customButtons[0].selected);
print("--")
var bt = customButtons.first{
$0.Id == 1
}
bt?.selected = true;
print(bt?.selected);
print(customButtons[0].selected);
here is the result
false
--
Optional(true)
false
the same applies to filter also!
Am I missing something or am I doing something wrong?
Note: I need to get the object that first or filter found and change it, not the hard copy of it

When you deal with a Struct, you have to understand that it is a value type.
So, that means that everytime you pass a value around, it's a COPY of the value and not the reference
And when you do this:
var bt = customButtons.first{
$0.Id == 1
}
You're asking the compiler to retrieve a copy of the CustomFilterButton whose Id is 1 and assign the copy to your bt variable.
To tackle this, you can access the element by its index and directly modify its value without passing it around (assigning to a new variable)
// Get the index of the element you're trying to modify
if let indexYouWantToModify = customButtons.firstIndex(where: {$0.Id == 1}){
// Modify it directly through its index
customButtons[indexYouWantToModify].selected = true
}
Oh, and though changing your Struct to a Class works for you, I think it's unnecessary just for this little use case. Structs and Classes hold their own benefits and trade-offs. I'm not sure what you're planning to do over this CustomFilterButton over the long run so I suggest you to read this article and decide for yourselves!

var bt = customButtons.first{
$0.Id == 1
}
At this moment bt has no any relation to your customButtons[0], its value was copied.

Access to item by index
print(customButtons[0].selected);
print("--")
var offset = 0
var bt = customButtons.enumerated().first{
if $0.element.Id == 1 {
offset = $0.offset
return true
}
return false
}
customButtons[offset].selected = true;
print(customButtons[0].selected);

You should use class instead of struct.

Related

How to put a variable in a for loop with different end number

I’ve got multiple variables with the same name but a different number on the end like this:
MyVar1, MyVar2, MyVar3, MyVar4
and they are either true or false
How do I write this correctly?
For index in Range(1...4){
if (“MyVar” + index) == true{
print(“:)”)
}
}
didnt tries the solution but according to this post: https://stackoverflow.com/a/45622489/5464805 you can do this:
Edit: I read that your variables are Booleans
for i in 0...4 {
if let var = myObject.value(forKey: "MyVar\(i)") as Bool,
var { // If var is successfully cast and true it enters the statement
print(var)
}
}
HOWEVER
All the other solutions above or below are still correct. This is something you dont usually do in Swift in general unless you must do so.
You should either rewrite your model and give a proper name to each variables, that you will later put into an array such as...
class MyObject {
var MyVar1: Bool // To Rename
var MyVar2: Bool // To Rename
var MyVar3: Bool // To Rename
var MyVars: [Bool] {
get { return [MyVar1, MyVar2, MyVar3] }
}
}
Or you completely get rid of these variable and create an Array directly
class MyObject {
// var MyVar1: Bool // To Delete
// var MyVar2: Bool // To Delete
// var MyVar3: Bool // To Delete
var MyVars: [Bool]
}
and you set the Array / Dictionnary in accordance to your needs
You are trying to get a sum of String ("MyVar") and Int ("index") and compare that sum with Bool (true)
It would be more safety to store all your variables in an array like that
let myVars = [MyVar1, MyVar2, MyVar3, MyVar4]
Then you can iterate throught the array:
for i in myVars {
if i {
print(i)
}
}
You can't create a variable from concatenation at runtime , you need to have an array of bool like this
var arr = [false,true,true,false] // var1 , var2 , var3 , var4
for item in arr {
if item {
}
}

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
}
}

filter array based on another

I have two arrays, one an array of array of profiles and one of the section names:
var sections: [Array<Profile>] = [friends, contacts, other]
var sectionNames = ["Friends", "Contacts", "Other Users"]
How do I filter the names based on whether the sections are empty? I get an error when I try the code below:
sectionNames.filter { index, _ in
sections[index].count > 0
}
Contextual closure type '(String) throws -> Bool' expect 1 argument, but two given
You can use zip and compactMap:
let nonEmptySections = zip(sections, sectionNames).compactMap { $0.isEmpty ? nil : $1 }
Using zip has the advantage that you don't get crashes if the two arrays have different sizes. On the other hand, it can lead to subtle bugs.
I'd recommend that you use a data structure to model you data instead:
struct Section {
let name: String
let profiles: [Profile]
}
This should ease the way you process the sections in your app. And by using #Alexander's suggestion, you can add an isEmpty property to the struct, making it even easier to use
extension Section {
var isEmpty: Bool { return profiles.isEmpty }
}
... later in the code
let nonEmptySections = sections.filter { !$0.isEmpty }
you can try something like this
var ar1:[Array<Int>] = [[1,2],[3,4,],[],[5,6]]
var ar2 = [1,2,3,4]
ar2 = (0 ..< ar1.count).filter {ar1[$0].count > 0}.map {ar2[$0]}
print(ar2) // [1, 2, 4]

How to create a pointer in Swift?

I'm working with Swift 3.
I would like to have this C syntax :
int myVar;
int *pointer = &myVar;
So modifying pointer or myVar does the same exact same thing.
Also I don't know if it makes any difference, but in my case myVar is an array containing elements of a class and pointer is a pointer to one element of this array.
The & also exists in Swift but can only be used as part of a parameter list (e.g. init, func, closure).
var i = 5
let ptr = UnsafeMutablePointer(&i)
print(ptr.pointee) // 5
// or
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
ptr.initialize(to: 5)
// or with a closure
let ptr: UnsafePointer = { $0 }(&i)
(Assuming I understand what you're asking for....)
Try the following code in a playground. It should print "99" three times.
class Row {
var rowNumber = 0
}
var rows = [Row]()
let testRow = Row()
testRow.rowNumber = 1
rows.append(testRow)
let selectedRow = rows[0]
selectedRow.rowNumber = 99
print(testRow.rowNumber)
print(selectedRow.rowNumber)
print(rows[0].rowNumber)
By default, there's no copying of objects as part of an assignment statement. If it were a struct, that would be different.
Adding a bit for completeness:
If you want a similar effect with scalar values instead of objects, Swift supplies various types of wrappers.
let intPointer = UnsafeMutablePointer<Int>.allocate(capacity: 8) // Should be 1, not 8 according to comment re: docs
let other = intPointer
other.pointee = 34
print(intPointer.pointee)
(Warning: I haven't used these wrappers for anything except experimenting in a playground. Don't trust it without doing some research.)
Same example as #Phillip. But I used struct. In this example rows[0] won't change:
struct Row {
var rowNumber = 0
}
var rows = [Row]()
var testRow = Row()
testRow.rowNumber = 1
rows.append(testRow)
var selectedRow = rows[0]
selectedRow.rowNumber = 99
print(testRow.rowNumber) // prints 1
print(selectedRow.rowNumber) // prints 99
print(rows[0].rowNumber) // prints 1
There are no C style pointers (Unsafe Pointer) as the question asks however objects are shared by reference and structures are by value:
Swift assign, pass and return a value by reference for reference type and by copy for Value Type
structures are always copied when they are passed around in your code, but classes are passed by reference.
For example
How to have pointers/ references to objects
class Song {
init(title: String, image: String, file: String, volume: Float, queuePlayer: AVQueuePlayer, playerLooper: AVPlayerLooper?) {
self.title = title
self.image = image
...
}
var title: String
var image: String
...
}
var aSong = Song(title: "", image: "", ...)
var arrOfSongReferences: [Song] = [Song]()
arrOfSongReferences.append(aSong)
var ptrToASong: Song = aSong
aSong = nil
// Due to Swift garbage collection ARC (Automatic Reference Counting), we still have references to the original aSong object so it won't be deleted
If data is struct you cannot do this
struct Song {
var title: String
var image: String
...
}
var aSong: Song = Song(title: "", image: "", ...)
var copyOfASong: Song = aSong
Method
You can also pass by reference into a function
// this would be inside a class, perhaps Player. It doesn't have to be a static btw
static func playSound(_ sound: inout Song, volume: Float = 0.0) {
if (sound.playerLooper == nil) {
...
}
}
// usage
Player.playSound(sound: &aSong)

Set Single Elements in Dictionary of Dictionaries

I have a question regarding a Dictionary of Dictionaries. Is there a shorter way to put single elements in the Dict than I did?
var cellHeight = [ Int : [ Int : CGFloat ] ]()
// ...
if let _ = cellHeight[0] {
cellHeight[0]![2] = 0.0
} else {
cellHeight[0] = [ 2 : 0.0 ]
}
In all the tutorials I checked it's only explained how to fill/initialize the full dict-of-dict and than read from it, but not how to fill it part by part.
Simply add the element. If an entry into a dictionary does not exist yet, adding an element to that key will create one. If a key already exists, then adding an element will overwrite the old one. Reading from a dictionary key that does not exist will return nil. Reading from a dictionary returns an optional.
What you're doing is basically right. You can be slightly more elegant, like this:
let didit = cellHeight[0]?[2] = 0 // yields an Optional<Void>
if didit == nil { // that didn't work, so create the entry
cellHeight[0] = [2:0]
}
And that can be tightened up even further, without the extra variable:
if nil == (cellHeight[0]?[2] = 0) {
cellHeight[0] = [2:0]
}
If this is a recurring pattern in your code, you can of course abstract it into a function.