-1

I have an empty array set as a global variable that is populated with array items from a tableview. This is used to populate another tableview. This data needs to persist so that when the user returns to the app, their tableview data is in the same state they left it, i.e. populate with data from the array.

Though I've looked for dozens of tutorials and examples. I've also hacked at it myself to make it work and every time I reload the app, the array is empty. How can I get that global variable array to hold onto it's array data?

var sharedData = [String]()

This is my 1st VC where I have setup functions for the UserDefaults. And I've executed my saveArray() func every time a change is made to the array. I've then executed retrieveArray() func every time I need to load from the array.

import UIKit

var sharedData = [String]()
struct Keys {

    static let arrayKey = "arrayKey"
}

let defaults = UserDefaults.standard

func saveArray() {
    defaults.set(sharedData, forKey: Keys.arrayKey)
}

func retrieveArray() {
    var savedData = defaults.object(forKey: Keys.arrayKey) as? [String] ?? []
    savedData.append(contentsOf: sharedData)
}

class ViewController: UIViewController {

    var effect:UIVisualEffect!

    @IBOutlet weak var searchBar: UISearchBar!

    @IBOutlet var tableView: UITableView!

    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!

    @IBOutlet weak var visualEffectView: UIVisualEffectView!

    let materialData = ["One", "Two", "Three", "Four"]

    var searchMaterial = [String]()
    var searching = false

    @IBAction func favoritesButtonArrayUpdate(_ sender: UIBarButtonItem) {
        print(sharedData)

    }

    override func viewDidLoad() {
        super.viewDidLoad()

    tableView.delegate = self
        tableView.dataSource = self

        saveArray()
        retrieveArray()

        print(sharedData)

    }

extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        tableView.deselectRow(at: indexPath, animated: true)
        print(self.materialData[indexPath.row], "selected!")
    }

    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {

        let favorite = UITableViewRowAction(style: .default, title: "Favorite") { (action, indexPath) in

            var data: String

            if self.searching {
                data = self.searchMaterial[indexPath.row]
            } else {
                data = self.materialData[indexPath.row]
            }

            sharedData.append(data)
            saveArray()
            print(sharedData)

        }

        favorite.backgroundColor = UIColor.orange
        return [favorite] 
    }

}

This is my 2nd VC which displays the array data stored in the global variable array sharedData. I've again added all the func when making changes to the array and pulling data from the array.

import UIKit

class FavoritesViewController: UIViewController {

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        saveArray()
        retrieveArray()
    }
}

extension FavoritesViewController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        retrieveArray()
        return sharedData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        retrieveArray()
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.numberOfLines = 0
        cell.textLabel?.text = sharedData[indexPath.row]
        return cell
    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {

            sharedData.remove(at: indexPath.row)
            saveArray()
            tableView.beginUpdates()
            tableView.deleteRows(at: [indexPath], with: .automatic)
            tableView.endUpdates()
        }
    }
}
pretz
  • 232
  • 2
  • 14

2 Answers2

1

The problem could be here:

let savedData: [String] = userDefaults.object(forKey: "arrayKey") as? [String] ?? []

Try changing it with:

let savedData: [String] = userDefaults?.object(forKey: "arrayKey") as? [String] ?? []

This is because UserDefaults must be unwrapped to refer to member object. Give it a try

MrHim
  • 401
  • 4
  • 16
  • Xcode responds with "Cannot use optional chaining on non-optional value of type 'UserDefaults'" and recommends removing the added "?" – pretz May 14 '20 at 22:10
  • 1
    so what exactly is the problem you have with the original version? (maybe update the question with the exact problem). What exactly you mean with "I need to restore the data present in the array upon a relaunch of the app", do you want them present on the relaunch or not ? ("restore" is a little tricky to uderstand). where are you going to use userDefaults ? Maybe you should consider to use suiteName. Take a look to Apple documentation – MrHim May 15 '20 at 07:37
  • I apologize, I don't think I have the right vocabulary to be very clear. Basically the app starts with an empty array (called sharedData), this gets populated by the user. When the user exits the app and reopens, the array is empty again. I need that data to remain in the array. Or to be saved elsewhere (userDefaults) and repopulated into the array when the app is reopened. I guess I need to be able to save the state of the array, if that is a thing. – pretz May 15 '20 at 11:23
  • 1
    That' s ok. If you write userDefaults.set(sharedData, forKey: "arrayKey") you are populating the key "arrayKey" of userDefaults with the datas in sharedData, but if you have just lunched the app, sharedData is empty, so if you try, immediatly after that, to populate savedDate with let savedData: [String] = userDefaults.object(forKey: "arrayKey") it would be empty bcause "arrayKey" in userDefaults is empty. So you need to populate "arrayKey" in userdefaults with shareData when shareData is populated by userinputs and then,on app launch, populate savedData from userdefaults (somewhere on start). – MrHim May 15 '20 at 13:52
  • 1
    not enough characters to be enough clear. What i mean is that you have to populate userdefaults in a different moment then populating savedData or you will get savedData equal to sharedData that is empty if you just lunched the app ( A = B, B = C, so A = C, if A = empty, C = empty). So find the right moment to store sharedData in userDefaults, and the right moment to populate savedData with Userdefaults contents. Maybe do the second thing in a launch class if you have it, or in a scene delegate for the launch or something else based on your project. – MrHim May 15 '20 at 13:55
  • Thank you for the advice. OK I've updated my post and added, basically, all of the code. I believe I've done what you mention. But it still is not working. I'm sure their is something small I'm doing wrong and I'm sure it's related to what you're saying about using UserDefaults at the wrong time to save an empty array. I just can't spot the point where I'm doing that. – pretz May 15 '20 at 14:03
  • This is bcause you call saveArray() on viewDidLoad so the first thing you do is to take datas from sharedData (that is empty) and put them in UserDefaults, so userdefaults will be empty now and when you call retrieveArray() it will populate savedData with an empty value (userdefaults arraykey). Maybe it is not clear that Userdefaults will store the persistant datas, not sharedData. And consider the needing of UserDefaults(suitename:) if you need to share datas with extensions. Take a look here [link](https://stackoverflow.com/questions/45607903/sharing-userdefaults-between-extensions/45608095) – MrHim May 15 '20 at 14:14
  • 1
    I got it sorted. No need to suitename:. I just had to get the workflow right. I was trying to complicate it too much. Thanks for your help! – pretz May 15 '20 at 16:44
0

Based on MrHim recommendations I removed the saveArray and retrieveArray func from the viewDidLoad of my first VC and left retrieveArray in viewDidLoad of my second VC. Having saveArray in my viewDidLoads was overwriting the array with empty data. I then needed to retrieve the array data in the proper place in my second VC. Then in my numberOfRowsInSection I removed retrieveArray.

pretz
  • 232
  • 2
  • 14