UICollectionView.reloadData() Design Error - swift

When i did cv.reloadData my cells is broken.
First
Last
When i write answer and click send button run this func.
func checkPlay(playedWord : String) {
if WordCheck(word: playedWord) {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
if isGameOver() {
//alert end save user default if score is max? and do proccess
}
}
}
If i get to comment cv.reloadData() func. My design is be safe but not writing answer.
I'm so sory for my bad English

Related

Why am I still able to fetch data, even with deleting FireStore object in Swift?

I deleted an entry in the Firestore and also checked it manually to confirm that. However, as long as I do not close the application, I can send a request to fetch the data and I still get the result. This should not be the case.
If you imagine having a shared photo with some textual information and you delete those information, this would mean, other users can still see the textual information (fetched from the Firestore) but not the image anymore (store in Firestorage).
I want to display a message on the UI, something like "The content does not exist anymore".
How I can achieve that? I used the following approach so far but it does not work at the moment:
public func checkIfChallengeObjectExists(completionHandler:#escaping(Bool)->(), challengeId:String) {
CHALLENGE_COLLECTION?.document(challengeId).getDocument(completion: { (querySnapshot, error) in
if (error != nil) {
print(error?.localizedDescription as Any)
}
if (querySnapshot?.documentID == "" || querySnapshot!.metadata.isFromCache) {
completionHandler(false)
}
else {
completionHandler(true)
}
})
}
Any solutions?
Non-existent documents will still return document snapshots, but they will be empty. Therefore, you must check the contents of the snapshot for the document, not the snapshot itself. Also, you should handle errors and the overall flow of the return better.
public func checkIfChallengeObjectExists(completionHandler:#escaping(Bool)->(), challengeId:String) {
CHALLENGE_COLLECTION?.document(challengeId).getDocument(completion: { (querySnapshot, error) in
if let doc = querySnapshot,
doc.exists {
completionHandler(true) // only one possible true condition
} else {
if let error = error {
print(error.localizedDescription)
}
completionHandler(false) // all else false
}
})
}
As a side note, I recommend reordering the parameters of the function to make it easier to read when called (conventionally, the completion handler comes last) and giving the boolean argument a name so it's easier to read when referencing (sometime later or by other developers).
public func verifyChallengeObject(ID: String, _ completion: #escaping (_ exists: Bool) -> Void) {
...
}
verifyChallengeObject(ID: "abc123", { (exists) in
if exists {
...
} else {
...
}
})

Label not being updated

I am trying to update a label to indicate status, but it is not being refreshed/shown as expected
func setStatusText(status: String) {
statusText.stringValue = status;
}
func doSomething() {
setStatusText(status: "Activating license"); // This is never shown
var rc = PerformAction()
if (rc == 0) {
setStatusText(status: "\(action) succeeded") // Correctly displayed
} else {
setStatusText(status: "\(action) failed") // Correctly displayed
}
The status update before the action is never being shown, but the status set right after the action is correctly displayed. What do I do to be sure the first status is shown?
Edited: I tried something else to try to isolate it. I added a button press, which will set text, sleep, and then set text again. I never see "first" in the status.
#IBAction func TestSettingText(_ sender: Any) {
self.statusText.stringValue = "first";
sleep(15)
self.statusText.stringValue = "second";
}
I don't know what exactly PerformAction does but it seems like it finishes quickly so you just can't see the Activating license status. It is there but only for a very brief moment and instantly changed to the next status

Swift: Control dependent functions, avoid callback hell

How can I design the following code better? I have the feeling that the code can lead to a callback hell. Every function depends on completion of the previous one.
Current Solution (bad):
#objc func restoreDocuments(UID: UID) {
DispatchQueue.global(qos: .background).async {
//1. Load user details from RemoteServer#1
UserManager.RemoteServer.loadUserFromRemoteServer(userUID: UID) { (userDict) in
//2. After user is loaded save user to local database
UserManager.LocalDB.saveUser(userData: userDict, completion: {
//After User is restored, restore his documents from RemoteServer#2 (IDs provided in userDetails)
let userDocumentsArray = getDocumentIDsFromUser(userUID: UID)
//Loop through array to get every ID
for ID in userDocumentsArray{
//load each document by ID
loadDocumentsRemote(documentID: ID) { (document) in
//Save loaded document
saveDocumentsLocal(document, completion: {
//At the end populate the UI with the restored documents
DispatchQueue.main.async {
populateUI()
}
})
})
}
})
}
}
I would imagine something like the following code. But I don't know how to communicate the different steps among each other. So that task 2 does not start before task 1 is finished.
What I imagine (simplified):
#objc func restoreDocuments(UID: UID) {
//1. Restore User
UserManager.RemoteServer.loadUser(UID){ (user) in }
UserManager.LocalDB.saveUser(user)
// -> WHEN FINISH PROCCED TO STEP 2 🚨
//2. Load Documents
UserManager.LocalDB.getDocumentIDsFromUser( { (IdArray) in
for ID in IdArray {
RemoteServer.DocManager.loadDocument(ID) { (retrievedDocument) in
LocalDB.DocManager.saveDocument(retrievedDocument)
}
}
}
// -> WHEN FINISH PROCCED TO STEP 3 🚨
//3. Finish
DispatchQueue.main.async {
populateUI()
}
}
But how do I do that? And is that a good approach at all 🤔?
Take a look at futures and promises, two related design patterns that address this issue very well. My company uses BrightFutures, a third party library that offers a decent implementation of both.
You can start by extracting the closures into variables:
let onDocumentsSaved: () -> Void = {
DispatchQueue.main.async {
populateUI()
}
}
let onDocumentsLoaded: (Document) -> Void { document in
saveDocumentsLocal(document, completion: onDocumentsSaved)
}
// continue with other closures, in reverse order of execution
That will clear up your indentation and the steps will be clearly visible. If you want to wait for multiple steps (e.g. multiple documents) in one step, you can use DispatchGroup.
Every step can be also easily extracted into a function, or, you can make your class to work as a state machine.
Also, it's a good idea to group connected methods into utility methods, e.g. your load and save can be grouped to one method with a completion handler, for example:
func loadUserAndSave(userId, completion)
func loadDocumentsAndSave(userId, completion)
then your method could be simplified to (pseudocode):
loadUserAndSave(userId) {
loadDocumentsAndSave {
DispatchQueue.main.async {
populateUI()
}
}
}
Which again would be much simpler .

Swift DispatchQueue with Firebase synchronously

I'm trying to fetch some data from firebase inside a for loop but it doesn't work.
I know I'm supposed to use DispatchQueue, but I can't understand how I'm supposed to use it with Firebase.
I got a loop:
for i in 0..<Exercice.workout.count{
exercice = (Exercice.workout[i] as! [[Any]])[0][0] as! String
print("Exercice: \(exercice)")
self.endDeleteLastSerie(key: key, exercice: exercice, callback: {(status, endTime) in
if status == "success"{
print("do stuff here")
let index = self.getIndexOfExercice(exerciceName: exercice)
print("Index: \(index)")
print("ExerciceName: \(exercice)")
}
}
Inside my function endDeleteLastSerie I'm calling 2 firebase functions
func endDeleteLastSerie(key:String, exercice: String, callback: #escaping(_ status:String, _ endTime: Int)->Void){
FirebaseWorkout.deleteSerie(key: key, exercice: exercice) { (status) in
if status == "success" {
//we set the end time to firebase
FirebaseWorkout.updateEndExercice(exercice: exercice, callback: { (status, endTime) in
if status == "success" {
callback("success", endTime)
}else{
callback("error", endTime)
}
})
}
}
}
**** Example of one of my firebase function ****
static func deleteSerie(key: String, exercice: String, callback: #escaping (_ status: String)->Void){
let uid = FirebaseUsers.User.uid
print("remove")
Database.database().reference().child("users/"+uid+"/workout/"+self.workoutKey+"/exercice/"+exercice+"/series/"+key).removeValue { (error, DatabaseReference) in
if error == nil {
print("removed from firebase")
callback("success")
}else{
callback("error")
}
}
}
But what I'm getting is:
Exercice: Bench Press
remove
Exercice: Pectoral Fly
remove
removed from firebase
removed from firebase
do stuff here
Index: 1
ExerciceName: Pectoral Fly
do stuff here
Index: 1
ExerciceName: Pectoral Fly
I tried to add my for loop inside:
DispatchQueue.main.sync { }
or
DispatchQueue.global().sync(execute: { })
or
var _dispatchQueue:DispatchQueue = DispatchQueue(label: "first", qos: .userInteractive)
then add my for loop inside
self._dispatchQueue.sync { }
But nothing work
How can I solve this ? and get
Exercice: Bench Press
remove
removed from firebase
do stuff here
Index: 0
ExerciceName: Bench Press
Exercice: Pectoral Fly
remove
removed from firebase
do stuff here
Index: 1
ExerciceName: Pectoral Fly
We don't have enough code here to understand portions of the application. However, it doesn't appear to be related to your Firebase code. How is the Exercise.workout array being managed?
In your first code snippet:
for i in 0..<Exercice.workout.count{
exercice = (Exercice.workout[i] as! [[Any]])[0][0] as! String
print("Exercice: \(exercice)")
self.endDeleteLastSerie(key: key, exercice: exercice, callback: {(status, endTime) in
if status == "success"{
print("do stuff here")
let index = self.getIndexOfExercice(exerciceName: exercice)
print("Index: \(index)")
print("ExerciceName: \(exercice)")
}
}
The for-loop is running through the Exercise.workout array and the functions you reference don't appear to be removing items from the array. Of course, that could be elsewhere in the code. Also, we don't know how the getIndexOfExercice function works.
Further, you are dealing with enclosures on different threads so the print statements can happen in various orders. If you want these to happen synchronously, you'll need to add code to do that.
There are many ways of synchronizing threads and there are plenty of examples on StackOverflow or general google searches.
However I don't think this is the crux of your question so if this doesn't help you find the problem, you will need to supply more code around the array being used (and getindex function).

Async callback for multiple loops in Swift 3

In one of the project I'm working on, I'm scheduling multiple notifications for different items in my database. Here's some code :
func setupNotifications() {
for medecine in medecineList! {
if !medecine.whichDaysArray.contains(Medecine.Days.EveryDay.rawValue) {
for day in medecine.whichDaysArrayDate {
for fTime in medecine.frequencyTimeDateArray {
self.createNotificationsForUniqueDays(parsedMedecine: medecine, parsedDay: day, parsedFTime: fTime)
}
}
}
else {
for fTime in medecine.frequencyTimeDateArray {
self.createNotificationsForEveryDay(parsedMedecine: medecine, parsedFTime: fTime)
}
}
}
self.callback()
}
func createNotificationsForEveryDay(parsedMedecine: Medecine, parsedFTime: Date) {
DispatchQueue.main.async {
// Create the notification
}
}
As you can see I deliberately put the function that handles creating the notifications on an async thread. The problem is that I'd like to call the method callback() when every async task has finished doing its job. I have no clue how to achieve this.
Any help, is appreciated thanks!
Method createNotificationsForEveryDay should have a block as parameter. In that block I would check to see if I processed the last item in frequencyTimeDateArray and if I did then I would call self.callback().