0

I have data that is saved with NSUserDefaults in an array of type "[RiskEntry]".

Let me explain to you what I save here and how the process of loading and saving looks like in my code (I took out code not relevant and I am using Swift3):

Main_ViewController

First: In the class Main_ViewController, I get a text transferred from a popover and save it in an array with NSUserDefaults in riskEntry with key "newTry" (this is just a temporary name ;-)) -> 'func riskText(riskTextTransferred: String)'.

Plan_ViewController

Second: when using a segue to get to the table on Plan_ViewController, the table is filled with the text saved in riskEntry with the key "newTry" (cell.riskTitle.text = savedTitles[indexPath.row].title as String). This all works fine so far.

CustomCell and Plan_ViewController

Third: When the user enters a value in the text field of a cell (consequences.text), the value consequences is updated for the riskEntry. So in the beginning, the array had an empty string for consequences. Now, the entry was updated with a text.

if let index = savedTitle.index(where: {$0.title.contains(identifier)}) {
            savedTitle[index].consequences = consequencesTranferred

The new string is then saved with the key "newTry". This also works like expected.

Main_ViewController

Fourth: Now I dismiss Plan_ViewController to go back to Main_ViewController. When I now try to load my Array in viewWillAppear() with key "newTry", it is empty.

What am I doing wrong? How can I successfully load the updated data from Plan_ViewController in Main_ViewController?

Main_ViewController

class Main_ViewController: UIViewController, UIPopoverPresentationControllerDelegate, UIGestureRecognizerDelegate {
    var riskItemDefaults = UserDefaults.standard
    var riskEntry = [RiskEntry]()

    override func viewWillAppear(_ animated: Bool) {

        if let dataArrayTitle = riskItemDefaults.object(forKey: "newTry") as? [NSData] {  
            var savedTitle = dataArrayTitle.map { RiskEntry(data: $0)! }
            riskItemDefaults.synchronize()

            // CHECKPOINT: all arrays are printed empty...
            print ("savedTitle: ")
            print (savedTitle)
            print ("dataArrayTitle: ")
            print (dataArrayTitle)
        } else {
           print ("nothing")
        }
    }


    // ---------------- delegate function that creates the array items -----------------
    func riskText(riskTextTransferred: String) {

        // CHECKPOINT: Array is saved correctly
        riskEntry = [RiskEntry(title: riskTextTransferred, consequences: "")]
        let encoded = riskEntry.map {$0.encode() }
        riskItemDefaults.set(encoded, forKey: "newTry")
        riskItemDefaults.synchronize()
    }
}

Plan_ViewController

class Plan_VC: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomCellUpdaterDelegate  {
    @IBOutlet weak var tableView: UITableView!
    var riskEntry = [RiskEntry]()
    var riskItemDefaults = UserDefaults.standard

    // ---------------- table settings -----------------
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        // load saved data and count number of entries for numberOfSections
        let dataArrayTitles = riskItemDefaults.object(forKey: "newTry") as! [NSData]
        let savedTitles = dataArrayTitles.map { RiskEntry(data: $0)! }

        return savedTitles.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = self.tableView!.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CellCustomized
        cell.delegate = self

            // load saved data and insert title in cell label
            let dataArrayTitles = riskItemDefaults.object(forKey: "newTry") as! [NSData]
            let savedTitles = dataArrayTitles.map { RiskEntry(data: $0)! }
            cell.riskTitle.text = savedTitles[indexPath.row].title as String

        return cell
    }

    // ---------------- delegate function from CustomCell -----------------
    func transferData(consequencesTranferred: String, identifier: String) {

        /// load entry
        let dataArrayTitle = riskItemDefaults.object(forKey: "newTry") as! [NSData]
        var savedTitle = dataArrayTitle.map { RiskEntry(data: $0)! }
        riskItemDefaults.synchronize()

        // CHECKPOINT: The entry is loaded correctly
        print("loaded: ")
        print(savedTitle)

        /// filter entry for title contains identifier and update the empty string for consequences
        if let index = savedTitle.index(where: {$0.title.contains(identifier)}) {
            savedTitle[index].consequences = consequencesTranferred
            let encoded = riskEntry.map { $0.encode() }
            riskItemDefaults.set(encoded, forKey: "newTry")
            riskItemDefaults.synchronize()

            // CHECKPOINT: The entry is updated correctly
            print("After updating for consequences: ")
            print(savedTitle)
        }else {
            print ("No title with value identifier to be filtered")
        }

    }
}

CustomCell

// ----------- delegate to transfer entered data to VC ------------
protocol CustomCellUpdaterDelegate {
    func transferData(consequencesTranferred: String, identifier: String)
}

// ---------------- start of calss CellCustomized -----------------
class CustomCell: UITableViewCell, UIPickerViewDataSource, UIPickerViewDelegate, UITextViewDelegate {

    var myColorsClass = myColors()
    var myStylesClass = myStyles()

    var delegate: CustomCellUpdaterDelegate?

    // text fields, text views and picker views
    @IBOutlet weak var riskTitle: UITextView!
    @IBOutlet weak var consequences: UITextView!

    var nameIdentifier = String()
    var textConsequences = String()


    override func awakeFromNib() {
        super.awakeFromNib()

        // initiate textView delegate
        consequences.delegate = self    

    } // nib end


    // ---------------- listener for text view to save input in string when editing is finished -----------------
    func textViewDidEndEditing(_ textView: UITextView) {
            textConsequences = consequences.text
            nameIdentifier = riskTitle.text
            delegate?.transferData(consequencesTranferred: self.textConsequences, identifier: nameIdentifier)
    }
}
Jake2Finn
  • 516
  • 5
  • 19
  • Basically `NSUserDefaults` is the wrong place to save temporary data especially data passed between view controllers. – vadian Oct 17 '16 at 11:44
  • My data is not supposed to be saved temporarily. The data should still be available after restarting the application... – Jake2Finn Oct 17 '16 at 11:56
  • I might have misunderstood your question, but you just want to pass data from Plan_VC to Main_VC, and then save this data, is that correct? If that's the case, you should use a delegate, because NSUserDefaults has time delay to save the data. – Dovahkiin Oct 17 '16 at 12:34
  • You say "dismiss Plan_ViewController". How is that done? – Phillip Mills Oct 17 '16 at 12:42
  • With a button in my navigation: 'func backButtonAction(_ sender:UIButton!) {dismiss(animated: true, completion: nil)}' – Jake2Finn Oct 17 '16 at 12:44

1 Answers1

0

Because there is a small time delay on NSUserDefaults, at the time you dismiss the Plan_ViewController,the newTry is not saved yet(it took some time to write into the disk I think), but the viewWillAppear() method in `Main_ViewController gets called immediately. Thus, due to the time delay, the array is empty.

To avoid that, well, you can't eliminate the time delay, what you can do is using a delegate to pass data back to your first VC.

So instead of retrieving the array from NSUserDefaults, you should do this directly from the delegate call.

Dovahkiin
  • 1,239
  • 11
  • 23
  • There shouldn't be a delay when using `synchronize`...assuming it's successful. – Phillip Mills Oct 17 '16 at 12:44
  • @PhillipMills Yes, there is an unnoticeable delay, probably 2ms. You can first make sure that your array is properly saved by printing it somewhere else(maybe still print in `viewWillAppear` but this time you can quit your app and relaunch, see if it prints the correct data). If true, then that small tiny delay is affecting your app. – Dovahkiin Oct 17 '16 at 12:48
  • If `synchronize` is actually asynchronous (i.e. data may not be written when it returns), that would be a bug according to its documentation. – Phillip Mills Oct 17 '16 at 12:52
  • Thanks for your help. I dropped the idea of using UserDefaults for my code but decided to switch to CodeData. That works much better now. – Jake2Finn Oct 22 '16 at 17:33