Dictionary Scope

I have a dictionary that I am trying to append information to but instead of appending to it just deletes the previous information. The dictionaries are in two different scopes. The dictionary name is personal info. They are on line 40 and 47. I tried doing personalInfo.append but it tells me that it has no member append. I am trying to save information to a database from to different views. This is the code that I have right now:


  @IBAction func sendPhoneAuthCode(_ sender: Any){
        if let phoneNum = phoneNumber.text {    // No more need to unwrap phoneNum later
            
            
            PhoneAuthProvider.provider().verifyPhoneNumber(phoneNum, uiDelegate: nil) { (verificationID, error) in
                
                // Sign in using the verificationID and the code sent to the user
                // …
                //  if phoneNum == nil {
                if error != nil {
                    print("verifyPhoneNumber went NOT OK")
                    // REPLACE BY print       self.present(alert, animated: true, completion: nil)
                    
                    
                    DispatchQueue.global(qos: .userInitiated).async {
                        let titre = " Missing Information "
                        let detailMsg = "Please enter your phone number"
                        let controller = UIAlertController(title: titre, message: detailMsg, preferredStyle: .alert)
                        
                        let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel)
                        controller.addAction(okAction)
                        DispatchQueue.main.async {
                            self.present(controller, animated: true, completion: nil)
                        }
                        
                    }
                    
                    
                    return
                } else {
                    print("verifyPhoneNumber went OK")
                    // You have to test credential here, where do you do this ?
                }
            }
        } else {
            print("phoneNumber.text is nil")
            return
        }
        personalInfo = ["Phone Number" : phoneNumber.text]
        print("Setting userDefaults", verificationID)
        UserDefaults.standard.set(verificationID, forKey: "authVerificationID")
    }


 @IBAction func savePersonalInfo(_ sender: Any) {
        personalInfo = ["First Name" : firstName.text, "Last Name" : lastName.text, "Email" : email.text, "Address Line 1" : addressLine1.text, "Address Line 2" : addressLine2.text]
        let ref = Database.database().reference()
        ref.childByAutoId().setValue(personalInfo)
    }

Accepted Reply

When you write a question of Dictionary, you should better include how you declared the variable, to get better answer sooner.


I assume:

var personalInfo: [String: Any] = [:]

If you declaration is something far from this, you may need to modify the following code.


And, when you want to modify or add some limited entries of a Dictionary and keep all other entries as is, you just need to use the subscript notation:

        personalInfo["Phone Number"] = phoneNumber.text ?? ""

or:

        personalInfo["Phone Number"] = phoneNumber.text ?? ""
        personalInfo["First Name"] = firstName.text ?? ""
        personalInfo["Last Name"] = lastName.text ?? ""
        personalInfo["Email"] = email.text ?? ""
        personalInfo["Address Line 1"] = addressLine1.text ?? ""
        personalInfo["Address Line 2"] = addressLine2.text ?? ""


Or else you can use `merge` method in your second case if you prefer:

        personalInfo.merge([
            "First Name" : firstName.text ?? "",
            "Last Name" : lastName.text ?? "",
            "Email" : email.text ?? "",
            "Address Line 1" : addressLine1.text ?? "",
            "Address Line 2" : addressLine2.text ?? ""] as [String: Any]
            , uniquingKeysWith: {_, new in return new})


You can omit `?? ""` in some cases.


And this is not the main topic of your question, but the lines 39.-41. should be in the completion handler of `verifyPhoneNumber` method.

Replies

When you write a question of Dictionary, you should better include how you declared the variable, to get better answer sooner.


I assume:

var personalInfo: [String: Any] = [:]

If you declaration is something far from this, you may need to modify the following code.


And, when you want to modify or add some limited entries of a Dictionary and keep all other entries as is, you just need to use the subscript notation:

        personalInfo["Phone Number"] = phoneNumber.text ?? ""

or:

        personalInfo["Phone Number"] = phoneNumber.text ?? ""
        personalInfo["First Name"] = firstName.text ?? ""
        personalInfo["Last Name"] = lastName.text ?? ""
        personalInfo["Email"] = email.text ?? ""
        personalInfo["Address Line 1"] = addressLine1.text ?? ""
        personalInfo["Address Line 2"] = addressLine2.text ?? ""


Or else you can use `merge` method in your second case if you prefer:

        personalInfo.merge([
            "First Name" : firstName.text ?? "",
            "Last Name" : lastName.text ?? "",
            "Email" : email.text ?? "",
            "Address Line 1" : addressLine1.text ?? "",
            "Address Line 2" : addressLine2.text ?? ""] as [String: Any]
            , uniquingKeysWith: {_, new in return new})


You can omit `?? ""` in some cases.


And this is not the main topic of your question, but the lines 39.-41. should be in the completion handler of `verifyPhoneNumber` method.

It still is losing the phone number when it changes the view. How would I fix this?

You need to show more details, other parts of your code, what you have done and what you see.

What do you mean « losing » ?


What is the value you pass ro the view ?

what do you get ?

Did you find what this number is (another record ? The previous value ? ...)

It's losing the phone number because that's what you told it to do. When you assign to a dictionary variable or property:


personalInfo = ["Phone Number" : phoneNumber.text]


you're saying that you want to throw away the current dictionary (and all its contents), and create a new dictionary with only these new contents.


You would fix this by not assigning new values to "personaInfo", but by replacing individual entries (keys/value pairs) as OOPer showed you.


Another way looking at this is that you're confusing the container and the contents. You can make a new container, but the old contents are in the old container. The new container doesn't get the old contents unless you copy them there. (But in this case, you don't need a new container, you just want to put more stuff in the old container.)

This is how my code looks now:


var personalInfo = [String : String?]()


  @IBAction func sendPhoneAuthCode(_ sender: Any){
        if let phoneNum = phoneNumber.text {    // No more need to unwrap phoneNum later
            
            
            PhoneAuthProvider.provider().verifyPhoneNumber(phoneNum, uiDelegate: nil) { (verificationID, error) in
                
                // Sign in using the verificationID and the code sent to the user
                // …
                //  if phoneNum == nil {
                if error != nil {
                    print("verifyPhoneNumber went NOT OK")
                    // REPLACE BY print       self.present(alert, animated: true, completion: nil)
                    
                    
                    DispatchQueue.global(qos: .userInitiated).async {
                        let titre = " Missing Information "
                        let detailMsg = "Please enter your phone number"
                        let controller = UIAlertController(title: titre, message: detailMsg, preferredStyle: .alert)
                        
                        let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel)
                        controller.addAction(okAction)
                        DispatchQueue.main.async {
                            self.present(controller, animated: true, completion: nil)
                        }
                        
                    }
                    
                    
                    return
                } else {
                    print("verifyPhoneNumber went OK")
                    // You have to test credential here, where do you do this ?
                }
            }
        } else {
            print("phoneNumber.text is nil")
            return
        }
        personalInfo["Phone Number"] = phoneNumber.text
        print("Setting userDefaults", verificationID)
        UserDefaults.standard.set(verificationID, forKey: "authVerificationID")
    }


    @IBAction func savePersonalInfo(_ sender: Any) {
        personalInfo["First Name"] = firstName.text
        personalInfo["Last Name"] = lastName.text
        personalInfo["Email"] = email.text
        personalInfo["Address Line 1"] = addressLine1.text
        personalInfo["Address Line 2"] = addressLine2.text
        print(personalInfo)
        let ref = Database.database().reference()
        ref.childByAutoId().setValue(personalInfo)
    }



The issue is still happening where it deletes the phone number.

You have a lot of "print" statements that will tell you what your code did. What's your evidence that the phone number was ever in the dictionary? When is the first time your code shows that it isn't there? What happens in between? (These are questions for you rather than the forum, since your code gives the answer.)

It saves it to the dictionary but when I go to the new view controller it deletes it and puts the new information in it. How do I save it to the dictionary even when I change view controllers?

You should better clarify that you go to the new view controller.

Where are the codes of the old view controller and the new view controller and how you move between view controllers?


Do you want to ask how do you store values into a Dictionary?

Or do you want to ask how do you persist a Dictionary?


How your Dictionary and database are related? And what is the database?

When you cannot solve your issue with a few responses, that's because your question does not contain needed information to solve your issue.


- Show all relevant codes. If your issue happens when moving to a new view controller, you may need to show both view contollers especially how you move.

- Clarify what you do when checking, for example

Enter phoneNumber in A-view controller

Move to B-view controller by pushing B-button in A view controller

Move back to A-view controller by pushing Back-button in the navigation bar of B-view controller

- Clarify what you have observed. It does not work or The issue is still happening or something like that has no info to solve your issue.

(In addition to the info when)

You checked the display in some of your view controller?

You used debugger?

You observed the output in the debug console from your `print` statements?


You need to show both expected result and actual result.

The codes for the new and old view controller in the same file.


The way that I get to a new view is with a show segue.


The reason that I tried to use a dictionary is that I was having a problem that I would save the information into the database from one view and it would be in one block in the json tree than I would go to another view and it would save the information to another block in json tree. I was trying to get all the information to be saved into one block. I am using the real-time database on Firebase. The code I have is what I showed to you.

If the two view controllers are the same, why do you move to new view controller?


The code I have is what I showed to you.


Your code never works without `class` header and some imports. All such things may affect. Please do not hide any lines of your code if you want better responses sooner.

We must tell Joel to learn to better use the forum by providing the right and complete information at first time and not hiding half of the page (in many cases the error is in the hidden part!)

The reason I move to a new view controller is because of the first view controller doubles as phone auth for firebase. How would I make it so that I can get the information into one block in the json tree?

because of the first view controller doubles as phone auth for firebase


Please be more specific. What happens when you use only one view controller, and explain why it harms.


I can get the information into one block in the json tree?


It is written in the first answer of mine. You may need to ask how to keep the content of the json tree between view controllers.


But the terminologie is not a big thing.

If you can describe what you have done and what you have seen, readers can guess what is happening even if some terms are ambiguous.


Do you never push any of the buttons in the screen nor manipulate any other UI controls?

If you do, please write it or them in order.

Do you never check the displayed contents in the text field or any other thing of the screen?

If you do, please write them in addition to when.


According to Claude31, you have some experience that the actual error is in the hidden part, not in the code you first have shown.

It is hard to say what would be the right and complete information when you do not know what is the cause of your issue,

But you could have shown whole code, I believe it is not too big to paste in an article.


Where do you write the declaration `var personalInfo = [String : String?]()`?

Is it inside the class definition? Or is a global variable which is placed outside of any classes?


You should better count the question marks in my comments, and check if you have answered all of them.

Here is the code from the whole view controller:


import UIKit
import FirebaseUI
import FirebaseAuth
import FirebaseDatabase

class signInViewController: UIViewController{
    //MARK: Properties
    @IBOutlet weak var phoneNumber: UITextField!
    @IBOutlet weak var firstName: UITextField!
    @IBOutlet weak var lastName: UITextField!
    @IBOutlet weak var email: UITextField!
    @IBOutlet weak var addressLine1: UITextField!
    @IBOutlet weak var addressLine2: UITextField!
    @IBOutlet weak var verificationCode: UITextField!
    @IBOutlet weak var phoneNumberContinueButton: CustomButton!
    var personalInfo = [String : String?]()
    let verificationID = UserDefaults.standard.string(forKey: "authVerificationID")
    
  
    
    //MARK: Sign In With Email
    
    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
    {
        if (textField == phoneNumber) {
            let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
            let components = newString.components(separatedBy: NSCharacterSet.decimalDigits.inverted)
            
            let decimalString = components.joined(separator: "") as NSString
            let length = decimalString.length
            let hasLeadingOne = length > 0 && decimalString.character(at: 0) == (1 as unichar)
            
            if length == 0 || (length > 10 && !hasLeadingOne) || length > 11 {
                let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int
                
                return (newLength > 10) ? false : true
            }
            var index = 0 as Int
            let formattedString = NSMutableString()
            
            if hasLeadingOne {
                formattedString.append("1 ")
                index += 1
            }
            if (length - index) > 3 {
                let areaCode = decimalString.substring(with: NSMakeRange(index, 3))
                formattedString.appendFormat("(%@)", areaCode)
                index += 3
            }
            if length - index > 3 {
                let prefix = decimalString.substring(with: NSMakeRange(index, 3))
                formattedString.appendFormat("%@-", prefix)
                index += 3
            }
            
            let remainder = decimalString.substring(from: index)
            formattedString.append(remainder)
            textField.text = formattedString as String
            return false
        }
        else {
            return true
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func passwordAuth(_ sender: Any) {
        let authUI = FUIAuth.defaultAuthUI()
        
        guard authUI != nil else{
            //Log the error
            return
        }
        
        authUI?.delegate = self
        
        let authViewController = authUI!.authViewController()
        
        present(authViewController, animated: true, completion: nil)
    }
    
    //MARK: Phone Auth
    @IBAction func sendPhoneAuthCode(_ sender: Any){
        if let phoneNum = phoneNumber.text {    // No more need to unwrap phoneNum later
            
            
            PhoneAuthProvider.provider().verifyPhoneNumber(phoneNum, uiDelegate: nil) { (verificationID, error) in
                
                // Sign in using the verificationID and the code sent to the user
                // …
                //  if phoneNum == nil {
                if error != nil {
                    print("verifyPhoneNumber went NOT OK")
                    // REPLACE BY print       self.present(alert, animated: true, completion: nil)
                    
                    
                    DispatchQueue.global(qos: .userInitiated).async {
                        let titre = " Missing Information "
                        let detailMsg = "Please enter your phone number"
                        let controller = UIAlertController(title: titre, message: detailMsg, preferredStyle: .alert)
                        
                        let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel)
                        controller.addAction(okAction)
                        DispatchQueue.main.async {
                            self.present(controller, animated: true, completion: nil)
                        }
                        
                    }
                    
                    
                    return
                } else {
                    print("verifyPhoneNumber went OK")
                    // You have to test credential here, where do you do this ?
                }
            }
        } else {
            print("phoneNumber.text is nil")
            return
        }
        personalInfo["Phone Number"] = phoneNumber.text
        print(personalInfo)
        print("Setting userDefaults", verificationID)
        UserDefaults.standard.set(verificationID, forKey: "authVerificationID")
    }

    
    @IBAction func enterVerificationCode(_ sender: Any){
        let credential = PhoneAuthProvider.provider().credential(
            withVerificationID: verificationID!,
            verificationCode: verificationCode.text!)
        
        Auth.auth().signInAndRetrieveData(with: credential) { (authResult, error) in
            if let error = error {
                // ...
                return
            }
            // User is signed in
            // ...
        }
    }
    
    @IBAction func savePersonalInfo(_ sender: Any) {
        personalInfo["First Name"] = firstName.text
        personalInfo["Last Name"] = lastName.text
        personalInfo["Email"] = email.text
        personalInfo["Address Line 1"] = addressLine1.text
        personalInfo["Address Line 2"] = addressLine2.text
        print(personalInfo)
        let ref = Database.database().reference()
        ref.childByAutoId().setValue(personalInfo)
    }
}

extension signInViewController: FUIAuthDelegate{
    func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?) {
        //Check if there was an error
        if error != nil{
            //Log the error
            return
        }
        
        authDataResult?.user.uid//Use for associating information in Firebase Database
        
        performSegue(withIdentifier: "passwordAuthLoggedIn", sender: self)
    }
}