0

I'm relatively new to Swift and am making a basic homework diary app. What I have so far is a UITableView embedded in a navigation controller which is embedded in a tab bar controller. I have successfully managed to implement a pull to refresh, adding data in a UIAlertView and populating the table with this data after the refresh. I need persistent data for this app and I heard that NSUserDefaults is the best way to do that, but it's not working for me. I can see that it is adding to the NSUserDefaults, but it doesn't seem to be reappearing in the UITableView after I close and reopen the app. Any suggestions? My code is below. Also, to put this data online, is there a way to use Google Sheets as an online database for my app?

import UIKit
var classesData = [String]()
var teachersData = [String]()
let defaults = NSUserDefaults.standardUserDefaults()
var tableData1 = defaults.valueForKey("classesData") as! NSArray
var tableData2 = defaults.valueForKey("teachersData") as! NSArray

class ClassesList: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPopoverPresentationControllerDelegate {
lazy var refreshControl: UIRefreshControl = {
    let refreshControl = UIRefreshControl()
    refreshControl.addTarget(self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged)

    return refreshControl
}()
@IBOutlet weak var tableView: UITableView!
@IBAction func addClass(sender: AnyObject) {
    var subjectTextField: UITextField?
    var teacherTextField: UITextField?
    let alertController = UIAlertController(title: "Add Class", message: "Please input the name of the subject and the teaceher", preferredStyle: .Alert)
    let done = UIAlertAction(title: "Done", style: .Default, handler: { (action) -> Void in
        classesData.append(subjectTextField!.text!)
        teachersData.append(teacherTextField!.text!)
        print(classesData)
        print(teachersData)
    })
    let cancel = UIAlertAction(title: "Cancel", style: .Cancel) { (action) -> Void in
    }
    alertController.addAction(done)
    alertController.addAction(cancel)
    alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
        // Enter the textfiled customization code here.
        subjectTextField = textField
        subjectTextField?.placeholder = "Subject"
    }
    alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
        // Enter the textfiled customization code here.
        teacherTextField = textField
        teacherTextField?.placeholder = "Teacher"
    }
    presentViewController(alertController, animated: true, completion: nil)

}

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "classCell")
    self.tableView.addSubview(self.refreshControl)


    // Do any additional setup after loading the view, typically from a nib.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.

}
func handleRefresh(refreshControl: UIRefreshControl) {
    defaults.setValue(classesData, forKey: "classesData")
    defaults.setValue(teachersData, forKey: "teachersData")
    print(tableData1)
    defaults.synchronize()
    self.tableView.reloadData()
    refreshControl.endRefreshing()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return classesData.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("classCell")! as UITableViewCell

    cell.textLabel?.text = (tableData1[indexPath.row] as? String)! + " , " + (tableData2[indexPath.row] as? String)!

    return cell
}

}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • The first thing you need to check is if the NSUserDefaults contains the elements you are looking for when you open the app! Check out the contents of NSUserDefaults inside viewDidLoad and see if it contains anything! – azamsharp Dec 06 '15 at 02:11
  • @Gautam Jethwani NSUserDefaults has a method called stringArrayForKey. You should take a look – Leo Dabus Dec 06 '15 at 03:03
  • [interesting read](http://stackoverflow.com/questions/7058858/should-i-use-nsuserdefaults-or-a-plist-to-store-data) – R Menke Dec 06 '15 at 03:25
  • If you say `UITableView` I immediately suspect that your data, whatever is in the table, is a little bit more complex. It might be worth your while to read up on Core Data and database structures. – R Menke Dec 06 '15 at 03:26

1 Answers1

0

At startup you use tableData1 & tableData2 to store information from defaults but classesData and teachersData are initialized to empty. When the app starts it calls numberOfRowsInSection and returns classesData.count which is zero. The other problem you have is that your app will crash the very first time it is ever run because you are initializing it with NSUserDefaults data which is empty. Reset the iOS Simulator then run your app and it will crash. You need to check and handle empty data from NSUserDefaults which is covered here: how to save and read array of array in NSUserdefaults in swift?

I would suggest the following: 1) remove tableData1 & tableData1 and replace with classesData and teachersData everywhere in the app. 2) Create a new function called loadUserDefaults() which is called in viewDidLoad which loads the data from NSUserDefaults while checking for empty NSUserDefaults data. 3) In handleRefresh() just save the data, reload the table view and end refresh. With these suggestions the working code is below.

import UIKit
var classesData = [String]()
var teachersData = [String]()
let defaults = NSUserDefaults.standardUserDefaults()

class ClassesList: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPopoverPresentationControllerDelegate {
    lazy var refreshControl: UIRefreshControl = {
        let refreshControl = UIRefreshControl()
        refreshControl.addTarget(self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged)

        return refreshControl
    }()
    @IBOutlet weak var tableView: UITableView!
    @IBAction func addClass(sender: AnyObject) {
        var subjectTextField: UITextField?
        var teacherTextField: UITextField?
        let alertController = UIAlertController(title: "Add Class", message: "Please input the name of the subject and the teaceher", preferredStyle: .Alert)
        let done = UIAlertAction(title: "Done", style: .Default, handler: { (action) -> Void in
            classesData.append(subjectTextField!.text!)
            teachersData.append(teacherTextField!.text!)
        })
        let cancel = UIAlertAction(title: "Cancel", style: .Cancel) { (action) -> Void in
        }
        alertController.addAction(done)
        alertController.addAction(cancel)
        alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
            // Enter the textfiled customization code here.
            subjectTextField = textField
            subjectTextField?.placeholder = "Subject"
        }
        alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
            // Enter the textfiled customization code here.
            teacherTextField = textField
            teacherTextField?.placeholder = "Teacher"
        }
        presentViewController(alertController, animated: true, completion: nil)

    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "classCell")
        self.tableView.addSubview(self.refreshControl)
        loadUserDefaults()
    }

    func handleRefresh(refreshControl: UIRefreshControl) {
        defaults.setValue(classesData, forKey: "classesData")
        defaults.setValue(teachersData, forKey: "teachersData")
        defaults.synchronize()
        self.tableView.reloadData()
        refreshControl.endRefreshing()
    }

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

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("classCell")! as UITableViewCell
        cell.textLabel?.text = (classesData[indexPath.row] as? String)! + " , " + (teachersData[indexPath.row] as? String)!

        return cell
    }

    func loadUserDefaults() {
        if let tempClassArray = defaults.valueForKey("classesData") {
            classesData = tempClassArray as! [String]
        }

        if let tempTeacherArray = defaults.valueForKey("classesData") {
            teachersData = tempTeacherArray as! [String]
        }
    }

}
Community
  • 1
  • 1
xdeleon
  • 769
  • 8
  • 20
  • When you have more than one question you should post that seperately, but the question regarding Google Sheets is already covered in this post where it recommends you use the Google Data API's: http://stackoverflow.com/questions/13211682/how-to-work-with-spreadsheets-using-google-drive-api-on-ios – xdeleon Dec 06 '15 at 18:11
  • Hey, I'll check out the Google Sheets link, but I tried the code you added, but it didn't work. Is there somewhere else I should put the valueforkey such as in the viewDidLoad method? – Gautam Jethwani Dec 07 '15 at 05:48
  • If you wanted to post the project as a .zip file and provide a link I could take a closer look at it. I don't think this would be an issue as it seems to be a class assignment. Here are possible ways you could do that: http://meta.stackexchange.com/questions/15821/stack-overflow-etiquette-for-sharing-files-and-resources – xdeleon Dec 07 '15 at 19:51
  • Guatam, I have updated my answer above now that I've reviewed your project. If this works just up vote +1 and if it does not work drop me another comment. – xdeleon Dec 08 '15 at 06:27
  • Hi xdeleon, At first, I tried to implement the changes you mentioned, but it didn't work. Then I copied and pasted your entire code and it worked, but it was displaying the subject twice. I changed one of the lines of code and it worked! Thank you so much! You're a lifesaver, you solved a problem I've been trying to solve for weeks. I'll try to find the difference between my code and your code. Thanks again! – Gautam Jethwani Dec 08 '15 at 11:58
  • Gautam Jethwani, if this did answer your question mark it as answered. You can do this at the the start of the answer to the left side where it currently shows zero. Tap the up arrow to change to "1". This helps to increase your reputation on this site as well as mine. Cheers. – xdeleon Dec 08 '15 at 15:31
  • Hi xdeleon, I clicked the up arrow but it keeps saying "Once you own a total of 15 reputation your votes will change the publicly displayed score" – Gautam Jethwani Dec 09 '15 at 15:32
  • Gautam Jethwani, Ok. Thx. Hope your assignment was completed. Cheers. – xdeleon Dec 09 '15 at 15:34