How to save the state of a table view?

How do I save the state of a table view after a change to the table view has been made? When I switch to another view and switch back, the table view changes are lost and is back to default. I know there are multiple ways to do this but what is the easiest way? I am thinking core data but how do I do it?

Accepted Reply

hi,


great! we've finally now gotten to what i think was your original question of "how to save the state ..." as i said above, "... if you really want to persist this data across application launches ... that's another journey."


what follows below is enough to get things working (albeit maybe not the best way to do this)


step 1: when the Database is first used, populate the allPlayers list from disk, or default to your hard-coded data if we have not yet written anything to disk.


first, change the name of your Data class to, perhaps, PlayerData -- you need to do this because we must use the Foundation class named Data -- and make it conform to the Codable protocol by writing


class PlayerData: Codable { // change name and add Codable


next, add the following functions to the Database class. you'll store the data in the Documents directory with the name playerData.json


fileprivate func playerDataURL() -> URL {
  let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).last!
  return documentDirectoryURL.appendingPathComponent("playerData.json")
}

fileprivate func readFromDisk() -> [PlayerData]? {
  let fileURL = playerDataURL()
  guard FileManager.default.fileExists(atPath: fileURL.path) else {
    return nil
  }

  do {
    let fileContents = try Data(contentsOf: fileURL)
    let list = try JSONDecoder().decode([PlayerData].self, from: fileContents)
    return list
  } catch let error as NSError {
    NSLog("Error reading file: \(error.localizedDescription)")
  }
  return nil
}


also change the init() method of Database slightly to be


init() {
  allPlayers = [PlayerData]()
  if let playerList = readFromDisk() {
    allPlayers = playerList
  } else {
    allPlayers = [
    // then all the data
    ]
  } // don't forget to add a closing curly brace here for the else
} // closes the init() method


step #2: add the following method in Database to be able to write data to disk.


fileprivate func writeToDisk() {
  do {
    let data = try JSONEncoder().encode(allPlayers)
    try data.write(to: playerDataURL())
  } catch let error as NSError {
    NSLog("Error reading file: \(error.localizedDescription)")
  }
}


step #3: you need to detect when changes have been made to the player data. one possible solution: explicitly tell the database that changes have been made whenever you make a change so that it will save to disk. so add the following method to the Database class:


func changeMade() {
  writeToDisk()
}


step #4: wherever in your code you make a change to any individual player, be sure to add a call to changeMade(). example: in addPlayer(), removePlayer(), and markAsTaken(), close the functions with both


top30QuarterbacksTable.reloadData()  // makes the visual changes on screen
Database.shared.changeMade() // triggers the data change on disk


i'm pretty sure i have all the code above correct on reading and writing (???)



hope that helps,

DMG

Replies

Thank you but where do you get playerData from in line 12 of step 2?

May be line #12 should be allPlayers.


Other point: did you call

initial_QuarterBacks()

in viewDidLoad


What result ?

Yes I did put it in viewDidLoad() and the table view still wouldn't save when returning to the view.


When using the database method, what do I return in


numberOfRowsInSection?


What do I set cell.textLabel?.text to in


cellForRowAt?



Thank you

oops, sorry -- should have been allPlayers (not playerData). i got caught between rewrites on that one ...


hope that helps,

DMG

Yes the database way works! I got it to work! The only thing now is that when I exit the app, all of the changes are lost. How do I keep the changes after quitting the app?

As you have found a solution that works, no need to elaborate much more.


Just to explain:

Most likely, you transition between views by dismissing previous view and loading new view (as with segue and unwindSegue).

You could have had 2 options at least:

First

- make the arrays global var, declaring them as optional

- in the first View, in viewDidload, only initialize if the array is nil. That way, you would load at first load but when you return after dismissing, you would not restet the data

Note that this is a way to make data persistent as long as the app is not closed.

Using dataBase is even more persistent, as data are saved when you quit the app.

But you will anyway loose data if you uninstall app, unless you save in Files.


Second

- do not dismiss views but stack them

- do do so, just embed the first view in a navigation controller

- navigation to the second with be a navigation link

- return from second with the back button.

hi,


great! we've finally now gotten to what i think was your original question of "how to save the state ..." as i said above, "... if you really want to persist this data across application launches ... that's another journey."


what follows below is enough to get things working (albeit maybe not the best way to do this)


step 1: when the Database is first used, populate the allPlayers list from disk, or default to your hard-coded data if we have not yet written anything to disk.


first, change the name of your Data class to, perhaps, PlayerData -- you need to do this because we must use the Foundation class named Data -- and make it conform to the Codable protocol by writing


class PlayerData: Codable { // change name and add Codable


next, add the following functions to the Database class. you'll store the data in the Documents directory with the name playerData.json


fileprivate func playerDataURL() -> URL {
  let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).last!
  return documentDirectoryURL.appendingPathComponent("playerData.json")
}

fileprivate func readFromDisk() -> [PlayerData]? {
  let fileURL = playerDataURL()
  guard FileManager.default.fileExists(atPath: fileURL.path) else {
    return nil
  }

  do {
    let fileContents = try Data(contentsOf: fileURL)
    let list = try JSONDecoder().decode([PlayerData].self, from: fileContents)
    return list
  } catch let error as NSError {
    NSLog("Error reading file: \(error.localizedDescription)")
  }
  return nil
}


also change the init() method of Database slightly to be


init() {
  allPlayers = [PlayerData]()
  if let playerList = readFromDisk() {
    allPlayers = playerList
  } else {
    allPlayers = [
    // then all the data
    ]
  } // don't forget to add a closing curly brace here for the else
} // closes the init() method


step #2: add the following method in Database to be able to write data to disk.


fileprivate func writeToDisk() {
  do {
    let data = try JSONEncoder().encode(allPlayers)
    try data.write(to: playerDataURL())
  } catch let error as NSError {
    NSLog("Error reading file: \(error.localizedDescription)")
  }
}


step #3: you need to detect when changes have been made to the player data. one possible solution: explicitly tell the database that changes have been made whenever you make a change so that it will save to disk. so add the following method to the Database class:


func changeMade() {
  writeToDisk()
}


step #4: wherever in your code you make a change to any individual player, be sure to add a call to changeMade(). example: in addPlayer(), removePlayer(), and markAsTaken(), close the functions with both


top30QuarterbacksTable.reloadData()  // makes the visual changes on screen
Database.shared.changeMade() // triggers the data change on disk


i'm pretty sure i have all the code above correct on reading and writing (???)



hope that helps,

DMG

Hello,


I made these changes and now I have a lot of errors. What am I doing wrong?


import Foundation
import UIKit

class PlayerData: Codable {
    var num: Int = 0
    var name: String = ""
    var team: String = ""
    var position: String = ""
    var strikeThrough: Bool = false
    var color: Bool = false
    var accessory: Bool = false
    
    init(num: Int, name: String, team: String, position: String) {
      self.num = num
      self.name = name
      self.team = team
      self.position = position
    }
}

var objectsArray = [PlayerData]()

class Database {
    
    static let shared = Database()
    
    fileprivate var allPlayers: [PlayerData]

    fileprivate func playerDataURL() -> URL {
      let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).last!
      return documentDirectoryURL.appendingPathComponent("playerData.json")
    }
      
    fileprivate func readFromDisk() -> [PlayerData]? {
      let fileURL = playerDataURL()
      guard FileManager.default.fileExists(atPath: fileURL.path) else {
        return nil
      }
      
      do {
        let fileContents = try Data(contentsOf: fileURL)
        let list = try JSONDecoder().decode([PlayerData].self, from: fileContents)
        return list
      } catch let error as NSError {
        NSLog("Error reading file: \(error.localizedDescription)")
      }
      return nil
    }
    
    fileprivate func writeToDisk() {
      do {
        let data = try JSONEncoder().encode(allPlayers)
        try data.write(to: playerDataURL())
      } catch let error as NSError {
        NSLog("Error reading file: \(error.localizedDescription)")
      }
    }
    
    func changeMade() {
      writeToDisk()
    }  
    
    func playerList(position: String) -> [Data] {
      return allPlayers.filter({ $0.position == position })
    }
    
    init() {
      allPlayers = [PlayerData]()
      if let playerList = readFromDisk() {
        allPlayers = playerList
      } else {
            allPlayers = [
                Data(num: 1, name: "Patrick Mahomes", team: "KC", position: "QB"),
                Data(num: 2, name: "Deshaun Watson", team: "HOU", position: "QB"),
                Data(num: 3, name: "Aaron Rodgers", team: "GB", position: "QB"),
                Data(num: 4, name: "Matt Ryan", team: "ATL", position: "QB"),
                Data(num: 5, name: "Baker Mayfield", team: "CLE", position: "QB"),
                Data(num: 6, name: "Carson Wentz", team: "PHI", position: "QB"),
                Data(num: 7, name: "Jared Goff", team: "LAR", position: "QB"),
                Data(num: 8, name: "Cam Newton", team: "CAR", position: "QB"),
                Data(num: 9, name: "Andrew Luck", team: "IND", position: "QB"),
                Data(num: 10, name: "Drew Brees", team: "NO", position: "QB"),
                Data(num: 11, name: "Ben Roethlisberger", team: "PIT", position: "QB"),
                Data(num: 12, name: "Dak Prescott", team: "DAL", position: "QB"),
                Data(num: 13, name: "Russell Wilson", team: "SEA", position: "QB"),
                Data(num: 14, name: "Kyler Murray", team: "ARI", position: "QB"),
                Data(num: 15, name: "Tom Brady", team: "NE", position: "QB"),
                Data(num: 16, name: "Lamar Jackson", team: "BAL", position: "QB"),
                Data(num: 17, name: "Mitchell Trubisky", team: "CHI", position: "QB"),
                Data(num: 18, name: "Jameis Winston", team: "TB", position: "QB"),
                Data(num: 19, name: "Philip Rivers", team: "LAC", position: "QB"),
                Data(num: 20, name: "Kirk Cousins", team: "MIN", position: "QB"),
                Data(num: 21, name: "Derek Carr", team: "OAK", position: "QB"),
                Data(num: 22, name: "Sam Darnold", team: "NYJ", position: "QB"),
                Data(num: 23, name: "Josh Allen", team: "BUF", position: "QB"),
                Data(num: 24, name: "Matthew Stafford", team: "DET", position: "QB"),
                Data(num: 25, name: "Marcus Mariota", team: "TEN", position: "QB"),
                Data(num: 26, name: "Jimmy Garoppolo", team: "SF", position: "QB"),
                Data(num: 27, name: "Andy Dalton", team: "CIN", position: "QB"),
                Data(num: 28, name: "Eli Manning", team: "NYG", position: "QB"),
                Data(num: 29, name: "Nick Foles", team: "JAC", position: "QB"),
                Data(num: 30, name: "Joe Flacco", team: "DEN", position: "QB"),
                
                // Running Backs
                Data(num: 1, name: "Saquon Barkley", team: "NYG", position: "RB"),
                Data(num: 2, name: "Christian McCaffrey", team: "CAR", position: "RB"),
                Data(num: 3, name: "Alvin Kamara", team: "NO", position: "RB"),
                Data(num: 4, name: "Ezekiel Elliott", team: "DAL", position: "RB"),
                Data(num: 5, name: "David Johnson", team: "ARI", position: "RB"),
                Data(num: 6, name: "Le'Veon Bell", team: "NYJ", position: "RB"),
                Data(num: 7, name: "Todd Gurley II", team: "LAR", position: "RB"),
                Data(num: 8, name: "Kerryon Johnson", team: "DET", position: "RB"),
                Data(num: 9, name: "Joe Mixon", team: "CIN", position: "RB"),
                Data(num: 10, name: "James Conner", team: "PIT", position: "RB"),
                Data(num: 11, name: "Dalvin Cook", team: "MIN", position: "RB"),
                Data(num: 12, name: "Nick Chubb", team: "CLE", position: "RB"),
                Data(num: 13, name: "Leonard Fournette", team: "JAC", position: "RB"),
                Data(num: 14, name: "Josh Jacobs", team: "OAK", position: "RB"),
                Data(num: 15, name: "Devonta Freeman", team: "ATL", position: "RB"),
                Data(num: 16, name: "Melvin Gordon", team: "LAC", position: "RB"),
                Data(num: 17, name: "Derrick Henry", team: "TEN", position: "RB"),
                Data(num: 18, name: "Chris Carson", team: "SEA", position: "RB"),
                Data(num: 19, name: "Marlon Mack", team: "IND", position: "RB"),
                Data(num: 20, name: "Aaron Jones", team: "GB", position: "RB"),
                Data(num: 21, name: "Damien Williams", team: "KC", position: "RB"),
                Data(num: 22, name: "Mark Ingram II", team: "BAL", position: "RB"),
                Data(num: 23, name: "James White", team: "NE", position: "RB"),
                Data(num: 24, name: "Sony Michel", team: "NE", position: "RB"),
                Data(num: 25, name: "Kenyan Drake", team: "MIA", position: "RB"),
                Data(num: 26, name: "David Montgomery", team: "CHI", position: "RB"),
                Data(num: 27, name: "Tarik Cohen", team: "CHI", position: "RB"),
                Data(num: 28, name: "Phillip Lindsay", team: "DEN", position: "RB"),
                Data(num: 29, name: "Derrius Guice", team: "WAS", position: "RB"),
                Data(num: 30, name: "Lamar Miller", team: "HOU", position: "RB"),
                Data(num: 31, name: "Tevin Coleman", team: "SF", position: "RB"),
                Data(num: 32, name: "Miles Sanders", team: "PHI", position: "RB"),
                Data(num: 33, name: "Austin Ekeler", team: "LAC", position: "RB"),
                Data(num: 34, name: "Duke Johnson Jr.", team: "HOU", position: "RB"),
                Data(num: 35, name: "Royce Freeman", team: "DEN", position: "RB"),
                Data(num: 36, name: "Rashaad Penny", team: "SEA", position: "RB"),
                Data(num: 37, name: "Nyheim Hines", team: "IND", position: "RB"),
                Data(num: 38, name: "Peyton Barber", team: "TB", position: "RB"),
                Data(num: 39, name: "Latavius Murray", team: "NO", position: "RB"),
                Data(num: 40, name: "Jordan Howard", team: "PHI", position: "RB"),
                Data(num: 41, name: "Matt Breida", team: "SF", position: "RB"),
                Data(num: 42, name: "LeSean McCoy", team: "BUF", position: "RB"),
                Data(num: 43, name: "Darrell Henderson", team: "LAR", position: "RB"),
                Data(num: 44, name: "Darwin Thompson", team: "KC", position: "RB"),
                Data(num: 45, name: "Ronald Jones", team: "TB", position: "RB"),
                Data(num: 46, name: "Devin Singletary", team: "BUF", position: "RB"),
                Data(num: 47, name: "Jalen Richard", team: "OAK", position: "RB"),
                Data(num: 48, name: "Dion Lewis", team: "TEN", position: "RB"),
                Data(num: 49, name: "Giovani Bernard", team: "CIN", position: "RB"),
                Data(num: 50, name: "Kalen Ballage", team: "MIA", position: "RB"),
            ]
        }
    }
}

Don't just say "lot of errors".


What can we do with this ?


Better show exactly what error and where.

hi,


as Claude31 said ... you're not being explicit about "what errors," and "where" they occur.


but i can see an obvious problem with your code -- and please let me apologize for not being even more explicit. i should not have assumed that you would follow through on the logical implications of proposing a name change.


since i asked that you rename the class "Data" to "PlayerData," so as to not conflict with Foundation's Data class, i assumed that you would then rewrite all of lines 73 - 154 to use "PlayerData" and not "Data." you didn't do that, and i think that's probably generating the errors you're seeing.


hope that helps,

DMG

Yes, I didn't realize I didn't change that. I should have known to change Data to PlayerData. It works great!


Thank you very much for your help.

I am sorry. I changed Data to PlayerData in the data and it works perfectly.


Thank you for your help.