Firestore iOS - Why does my array get printed out multiple times? - ios

I'm successfully able to display a users friends array in a table view, but when I print out the array it get's printed out 3 times instead of 1 time, how do I fix this?
Friend System model:
var removeFriendListener: ListenerRegistration!
func addFriendObserver(_ update: #escaping () -> Void) {
removeFriendListener = CURRENT_USER_FRIENDS_REF.addSnapshotListener{ snapshot, error in
self.friendList.removeAll()
guard error == nil else {
#if DEBUG
print("Error retreiving collection")
#endif
return
}
for document in snapshot!.documents {
let id = document.documentID
self.getUser(id, completion: { (user) in
self.friendList.append(user)
update()
})
}
if snapshot!.documents.count == 0 {
update()
}
}
}
func removeFriendObserver() {
removeFriendListener.remove()
}
Friend System View Controller:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
FriendSystem.system.addFriendObserver { () in
DispatchQueue.main.async {
self.tableView.reloadData()
}
print(FriendSystem.system.friendList)
}
}
Array printed out
[App.User]
[App.User, App.User]
[App.User, App.User, App.User]

you should not call your update() here:-
for document in snapshot!.documents {
let id = document.documentID
self.getUser(id, completion: { (user) in
self.friendList.append(user)
update()
})
}
instead you should call it after the for loop gets completed.

Related

want to set a self var using guard statement

I have this code, where I'm trying to set a self variable (self?.users) from a view model call. The code snippet looks like this.
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.global().async { [weak self] in
self?.model?.findAll() { [weak self] users, exception in // network call
guard users != nil, self?.users = users else { // Optional type ()? cannot be used as a boolean; test for !=nil instead
}
}
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
I'm capturing [weak self] twice, is that okay?, can I capture it once as weak in the enclosing closure?
Should I use this instead of guard statement?
self?.model?.findAll() { [weak self] users, exception in
if exception != nil {
self?.users = users
}
}
DispatchQueue closures don't cause retain cycles so capture lists are not necessary.
Something like this, to avoid confusion I'd recommend to rename the incoming users and the code to reload the table view must be inside the closure
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global().async {
self.model?.findAll() { [weak self] foundUsers, exception in // network call
guard let foundUsers = foundUsers else { return }
self?.users = foundUsers
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
}
And don’t forget to call super

getAllVoiceShortcuts function returns twice

I'm fetching voiceShortcurts related with my app. Below function goes into completion block twice. It returns true at first, then false which true is the right one. Why It goes into completion block twice?
public static func updateVoiceShortcuts(completion: #escaping ((_ haveShortcutAlready: Bool) -> Void)) {
INVoiceShortcutCenter.shared.getAllVoiceShortcuts { (voiceShortcutsFromCenter, error) in
if let voiceShortcutsFromCenter = voiceShortcutsFromCenter {
self.voiceShortcuts = voiceShortcutsFromCenter
completion(true)
} else {
if let error = error as NSError? {
print(error)
}
completion(false)
}
}
}

Firebase Anonymous Auth, Nil User

I'm using Firebase database and offer anonymous login. The first anonymous login made on a single device works as expected. If I sign out and attempt any more anonymous logins, it succeeds, the completion block has no error and returns a user.
However, once it's all done and we're out of the completion block, Auth.auth().currentUser() is nil.
If I run a simple Timer checking Auth.auth().currentUser() every second, throughout the entire login process it is always nil and never changes.
Quick breakdown of code:
Login anonymously.
Check if id exists in db.
Update profile displayName with id for easy referral later.
Fetch client in db.
All go wrong!
Tap a button to sign in.
#IBAction func clientLoginBtnTap(_ sender : UIButton) {
self.clientActivityIndicator?.showActivityIndicator()
Auth.auth().signInAnonymously { (user, error) in
if error == nil {
//check id matches available client
self.checkClient(id: (self.clientIdField?.text)!, completion: { (isValid) in
if isValid == true {
//now signed in, update client id
let profileChangeRequest = user?.createProfileChangeRequest()
profileChangeRequest?.displayName = self.clientIdField?.text
profileChangeRequest?.commitChanges(completion: { (error) in
if error == nil {
//done
UserDefaults.standard.set(true, forKey: kIS_USER_CLIENT_NOT_TRAINER)
self.dismiss(animated: true, completion: {
//self.delegate?.didLoginAsClient()
})
}
else {
self.logout()
self.clientIdField?.shake()
self.clientActivityIndicator?.hideActivityIndicator()
}
})
}
else {
self.logout()
self.clientIdField?.shake()
self.clientActivityIndicator?.hideActivityIndicator()
}
})
}
else {
}
}
}
func checkClient(id : String, completion: #escaping (_ isValid : Bool) -> Void) {
let ref = Database.database().reference().child("v2").child("clients").child(id)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() { completion(true) }
else { completion(false) }
}) { (error) in
completion(false)
}
}
func logout() {
do {
try Auth.auth().signOut()
}
catch let error as NSError {
print (error.localizedDescription)
}
}
Login is successful.
Then this runs after login and the user exists but Auth.auth().currentUser() is nil. When a client login happens, I try to get the client data but permission is denied because we have no user.
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
self.currentUser = user
if user == nil {
self.updateForNoUser()
}
else {
self.updateForUser()
}
}
func updateForUser() {
//Trainer Logged in
if UserDefaults.standard.bool(forKey: kIS_USER_CLIENT_NOT_TRAINER) == false {
self.performSegue(withIdentifier: "master", sender: self)
}
//Client Logged in
else {
if let id = Auth.auth().currentUser?.displayName {
let ref = Database.database().reference().child("v2").child("clients").child(id)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
self.client = Client(snapshot: snapshot)
self.performSegue(withIdentifier: "masterClient", sender: self)
}
}) { (error) in }
}
else {
do {
try Auth.auth().signOut()
}
catch let error as NSError {
print (error.localizedDescription)
}
}
}
}

Updating a UILabel from an escaping closure doesn't take effect immedialtely

Basically:
In viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
myFunction(closure)
}
closureis of type #escaping (_ success: Bool) -> Void
In the closure code:
print("Change myLabel.text")
self?.myLabel.text = "New Title"
print("myLabel.text changed")
"Show myLabel.text" and "myLabel.text changed" are printed as soon as the VC appears, but the text in myLabel changes after several seconds (around 10 seconds).
myLabel is created programmatically as seen below:
class MyClass : UIViewController {
...
var myLabel: UILabel!
var contacts = [ContactEntry]()
...
override func viewWillLayoutSubviews() {
myLabel = UILabel()
myLabel.text = "Original title"
myLabel.frame = CGRect(x: 10, y: 10, width: 100, height: 400)
self.view.addSubview(myLabel)
}
}
The actual code is inspired from here:
viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
requestAccessToContacts { [weak self] (success) in
if success {
self?.retrieveContacts(completion: { (success, contacts) in
self?.tableView.isHidden = false
self?.myLabel.isHidden = true
if success && (contacts?.count)! > 0 {
self?.contacts = contacts!
self?.myLabel.text = ""
self?.myLabel.isHidden = true
self?.tableView.reloadData()
} else if (contacts?.count)! == 0 {
self?.myLabel.isHidden = false
self?.myLabel.text = "No contacts found"
} else {{
self?.myLabel.isHidden = false
self?.myLabel.text = "Error loading contacts"
}
})
} else {
print("Change label text")
self?.myLabel.attributedText = "Enable access to contacts by going to\nSettings>Privacy>Contacts>MyApp"
self?.myLabel.isHidden = false
print("Label text changed")
}
}
}
requestAccessToContacts:
func requestAccessToContacts(completion: #escaping (_ success: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts)
switch authorizationStatus {
case .authorized:
// authorized previously
completion(true)
case .denied, .notDetermined:
// needs to ask for authorization
self.contactStore.requestAccess(for: CNEntityType.contacts, completionHandler: { (accessGranted, error) -> Void in
completion(accessGranted)
})
default:
// not authorized.
completion(false)
}
}
retrieveContacts:
func retrieveContacts(completion: (_ success: Bool, _ contacts: [ContactEntry]?) -> Void) {
var contacts = [ContactEntry]()
do {
let contactsFetchRequest = CNContactFetchRequest(keysToFetch: [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactImageDataKey, CNContactImageDataAvailableKey, CNContactPhoneNumbersKey, CNContactEmailAddressesKey].map {$0 as CNKeyDescriptor})
try contactStore.enumerateContacts(with: contactsFetchRequest, usingBlock: { (cnContact, error) in
if let contact = ContactEntry(cnContact: cnContact) { contacts.append(contact) }
})
completion(true, contacts)
} catch {
completion(false, nil)
}
}
What am I missing here?
You are saying:
print("Change myLabel.text")
self?.myLabel.text = "New Title"
print("myLabel.text changed")
And you are complaining that the print messages appear in the console but the label doesn't change until much later.
This sort of delay is nearly always caused by a threading issue. You do not show MyFunction and you do not show the entirety of closure, so it's impossible to help you in detail, but the likelihood is that you are messing around with background threads without knowing what you are doing, and that you have accidentally set myLabel.text on a background thread, which is a big no-no. You must step out to the main thread in order to touch the interface in any way:
DispatchQueue.main.async {
print("Change myLabel.text")
self?.myLabel.text = "New Title"
print("myLabel.text changed")
// ... and everything else that touches the interface
}

Firebase adding data to Array but still empty

As you can see here
import UIKit
class Connecting {
let refHandler = Firebase(url: "https://-unique-link-.firebaseio.com/handlers")
var handlerID: [AnyObject] = []
func getHandlerStatus() {
refHandler.queryOrderedByChild("status").observeEventType(.ChildAdded, withBlock: { snapshot in
if let status = snapshot.value["status"] as? Int {
if status == 0 {
self.handlerID.append(snapshot.key)
print(self.handlerID)
} else {
//Do Nothing
}
}
})
}
func handlerIDReturn() -> NSArray {
print(handlerID)
return handlerID
}
}
handlerID array is filled but when i check, its empty. :( please help
Ive updated with all the codes.
Here is a calling code. I use button to call each fund
#IBAction func callerButton(sender: AnyObject) {
Connecting().getHandlerStatus()
}
#IBAction func button2nd(sender: AnyObject) {
print(Connecting().handlerIDReturn())
}
You need to create just one instance of Connecting and use that. At the moment, every call to Connecting() is giving you a new object.
let connecting = Connecting()
#IBAction func callerButton(sender: AnyObject) {
connecting.getHandlerStatus()
}
#IBAction func button2nd(sender: AnyObject) {
print(connecting.handlerIDReturn())
}

Resources