Learn Courses My Dashboard

Copying array out of a struct

I feel embarrassed asking questions here. Accordingly, I’ve tried 100 different things before resorting to asking you non-judgmental and generous wizards…

My model:

struct User: Identifiable {
    var id = ""
    var nickname = ""
    var iLike = [""]
    var likeMe = [""]
}

My content model:

class ContentModel: ObservableObject {
    
    let db = Firestore.firestore()
    
    @Published var myFriends = [User]()
    @Published var usersWhoILike = [User]()
    @Published var usersWhoLikeMe = [User]()

    
    
    
    func getCurrentUserData() {

        // This function works, with the exception of the call to self.getMyFriends() at the end.
        
        let ref = db.collection("users").document(Auth.auth().currentUser!.uid)
        
        ref.getDocument { [self] snapshot, error in
            
            guard error == nil, snapshot != nil else {
                return
            }
            
            let data = snapshot!.data()
            let currentUser = UserService.shared.currentUser
            currentUser.id = Auth.auth().currentUser!.uid
            currentUser.nickname = data?["nickname"] as? String ?? ""            

            self.getUsersWhoLikeMe()
            self.getUsersWhoILike()

            // This is where I run into problems.
            self.getMyFriends()
        }
    }
    
    
    
    func getUsersWhoILike() {

       // this function works (My previous problem with this function was not with the code, but the data. Thank, Chris, for pointing me in the right direction.)
        
        let collection = db.collection("users").whereField("likeMe", arrayContains: Auth.auth().currentUser!.uid)

        collection.getDocuments { snapshot, error in
            if error == nil {
                
                // declare a temp list of users
                var userList = [User]()
                
                for doc in snapshot!.documents {
                    
                    let user = User(
                        id: doc["id"] as? String ?? "",
                        nickname: doc["nickname"] as? String ?? "",
                        iLike: doc["iLike"] as? [String] ?? [""],
                        likeMe: doc["likeMe"] as? [String] ?? [""]
                    )
                    
                    userList.append(user)
                }
                
                DispatchQueue.main.async {
                    self.usersWhoILike = userList
                }
            }
        }
    }
    
    
    
    func getUsersWhoLikeMe() {
        
        // This function is essentially identical to the function above, except it queries different data. It works.
    }
    
    
    
    func getMyFriends() {

        // This is where I am running into problems.

        // self.usersWhoILike.count and self.usersWhoLikeMe.count both return 0, which I know isn't true as I am using this very data in my Views. (They should return 3 and 1, respectively, given the data.)
        print("Users who I like: \(self.usersWhoILike.count)")
        print("Users who like me: \(self.usersWhoLikeMe.count)")
        
        // I would like to extract the ids from User into its own array, but can't figure out the syntax
        // This is my best approximation, which doesn't work.
        // var usersWhoILikeIDs = self.usersWhoILike[$0].id

        // The above line of code not working, this is my less elegant workaround.
        var usersWhoILikeIds = [""]
        for user in usersWhoILike {
            usersWhoILikeIds.append(user.id)
        }

        var usersWhoLikeMeIds = [""]
        for user in usersWhoLikeMe {
            usersWhoLikeMeIds.append(user.id)
        }

        // Compare the two arrays to generate a list of ids of the users who I like and like me.
        let usersWhoAreMyFriends = usersWhoILikeIds.filter { usersWhoLikeMeIds.contains($0) }
    }

My main issue is figuring out why self.usersWhoILike.count and self.usersWhoLikeMe.count are zero. Nothing else matters if I can’t access the data in these properties.

Figuring out who to copy an array out of a struct into another array is really a bonus question, as I think the code should work without it … but I sure would like to learn the right way to do this.

Any thoughts?

It looks like the issue you’re having is that the values of self.usersWhoILike and self.usersWhoLikeMe are not being set correctly when you call self.getMyFriends() . This could be because the asynchronous calls to getUsersWhoILike and getUsersWhoLikeMe have not completed when getMyFriends is called.

To fix this, you could try moving the call to self.getMyFriends() to inside the completion block of getUsersWhoLikeMe or getUsersWhoILike . This will ensure that the values of self.usersWhoILike and self.usersWhoLikeMe have been set before getMyFriends is called.

For example, you could try the following:

func getCurrentUserData() {
    let ref = db.collection("users").document(Auth.auth().currentUser!.uid)
    
    ref.getDocument { [self] snapshot, error in
        guard error == nil, snapshot != nil else {
            return
        }
        
        let data = snapshot!.data()
        let currentUser = UserService.shared.currentUser
        currentUser.id = Auth.auth().currentUser!.uid
        currentUser.nickname = data?["nickname"] as? String ?? ""            

        self.getUsersWhoLikeMe()
        self.getUsersWhoILike()
    }
}

func getUsersWhoILike() {
    let collection = db.collection("users").whereField("likeMe", arrayContains: Auth.auth().currentUser!.uid)

    collection.getDocuments { snapshot, error in
        if error == nil {
            var userList = [User]()
            
            for doc in snapshot!.documents {
                let user = User(
                    id: doc["id"] as? String ?? "",
                    nickname: doc["nickname"] as? String ?? "",
                    iLike: doc["iLike"] as? [String] ?? [""],
                    likeMe: doc["likeMe"] as? [String] ?? [""]
                )
                
                userList.append(user)
            }
            
            DispatchQueue.main.async {
                self.usersWhoILike = userList
                self.getMyFriends()
            }
        }
    }

As far as this…

For this, why not do:

let user = User(id: "1", nickname: "Ted", iLike: ["Bonnie", "George"])
// Access the iLike property and assign to a new list
let userLikesArray = user.iLike
print(userLikesArray)
// prints ["Bonnie", "George"]

Update: From your comments I see you wanted to get a new array of IDs from your array of users. To do that, you can use this single line of code:

var usersWhoILikeIDs = usersWhoILike.map { $0.id }

The map method applies something to each element in a list, then returns it as a new list. So above, I’m saying iterate through each of the users, where $0 represents the user, and for each user, return the ID. I learned how to use this from Stewart Lynch’s video on map (he also did an excellent Async class for CWC+)

Otherwise, it’s equivalent to the following code as well:

let idNumbers = userList.map { user in
    return user.id
}

// For loop method
var idList = [String]()
for user in userList {
    idList.append(user.id)
}
1 Like

First of all, thank you for taking the time to help me with this. I really appreciate it.

Both of your suggestions worked.

Thanks!

1 Like

You’re most welcome, and great news!! Thanks for posting - it was a fun puzzle to solve. I would’ve been shaking my head at that for days too, but had just gone through Lynch’s Async class, so thought that some properties were not loaded yet. Thanks for posting all the code to view the problem as well.