How do I save a table view?

How do I save the view of a table view. I use a strike through text, an accessory type check, and other attributes to the cell at indexPath. How do I save it? I can't even switch to a different view without it not staying.

Accepted Reply

hi,


there are a whole bunch of things going on here that you need to address first, before moving along to any discussion of persisting data (if that's really what you mean, as in saving to disk).


(1) your data consists only of objectsArray: [String], but once the tableview is drawn for you, you try to then store/update the state of the objects in the attributedText field of each cell.


that's bad. you should perhaps keep a separate, parallel array to keep track of the additional state of each string in the object array. or, better still, your objectsArray should be an array of structs, where each struct is something like


struct Quarterback {

var name: String

var strikeThrough: Bool

}

when cellForRowAtIndexPath is called, get the information from objectsArray and fill in both the text and attributed text of the cell you have dequeued. example sequence:


let cell = top30QuarterbacksTable.cellForRow(at: indexPath)

let player: Quarterback = objectsArray[indexPath.row]

cell.textLabel?.text = "\(indexPath.row+1). " + player.name

cell.textLabel?.adjustsFontSizeToFitWidth = true

cell.textLabel?.font = UIFont.systemFont(ofSize: 22)

if player.strikeThrough {

cell?.textLabel?.attributedText = strikeThroughText(player.name)

} else {

cell?.textLabel?.attributedText = nil

}

return cell


you're also apparently trying to tweak a cell's accessoryType elsewhere in your code -- that's something that should also set up here and be tracked in the Quarterback struct by another variable.


(2) if you want to know what's in a given row of the table at anytime, don't ask cellForRowAtIndexPath for the cell (note: the cell you get back will not be the cell that's displayed on screen, but only a new cell with the information you're looking for that you already have). you know what will be in the row just by looking at the objectsArray. and cellForRowAtIndexPath is really something you want to let the system call -- it's not something you want to call on your own.


an example (just kill the commented-out lines)


func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

// let cell = self.top30QuarterbacksTable.cellForRow(at: indexPath)

// let str: String = (cell?.textLabel!.text)!

let player: Quarterback = objectsArray[indexPath.row]

// if cell?.textLabel?.attributedText == strikeThroughText(str) {

// strikeThroughTextBool = true

// } else {

// strikeThroughTextBool = false

// }

// if strikeThroughTextBool == false

if player.strikeThrough {

......

}

note that addPlayer and RemovePlayer have the same issues at the start of their code.


(3) call top30QuarterbacksTable.reloadData() whenever you make any changes to the objectsArray -- don't try to do anything with cells directly. reloadData() will cause all the cells to be redrawn.


for example, each of your addPlayer and removePlayer should end with a call to reloadData().




hope that helps get you started ... happy to discuss more when you clean up some of these issues.


hope that helps,

DMG

Replies

func strikeThroughText (_ text:String) -> NSAttributedString {
    let attributeString: NSMutableAttributedString =  NSMutableAttributedString(string: text)
    attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 1, range: NSMakeRange(0, attributeString.length))
    return attributeString
    }

func noStrikeThroughText (_ text: String) -> NSAttributedString {
    let attributeString: NSMutableAttributedString =  NSMutableAttributedString(string: text)
    attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 0, range: NSMakeRange(0, attributeString.length))
    return attributeString
}

var strikeThroughTextBool: Bool = false

So the attributed text does not contain num.


Could change like this :


if objectsArray[indexPath.row].strikeThrough == false && objectsArray[indexPath.row].accessory == false && objectsArray[indexPath.row].color == false {
    cell.textLabel?.text = "\(num).  \(name)"
    cell.textLabel?.attributedText = noStrikeThroughText("\(num).  \(name)")
    cell.accessoryType = UITableViewCell.AccessoryType.none
    cell.backgroundColor = .none
}
else if objectsArray[indexPath.row].strikeThrough == true && objectsArray[indexPath.row].accessory == true && objectsArray[indexPath.row].color == true {
    cell.textLabel?.text = "\(num).  \(name)"
    cell.textLabel?.attributedText = strikeThroughText("\(num).  \(name)")
    cell.accessoryType = UITableViewCell.AccessoryType.checkmark
    cell.backgroundColor = .systemGray3
} else {
    cell.textLabel?.text = "\(num).  \(name)"
    cell.textLabel?.attributedText = strikeThroughText("\(num).  \(name)")
    cell.accessoryType = UITableViewCell.AccessoryType.none
    cell.backgroundColor = .systemGray2
}

Note: lines 2, 8 and 13 are superfluous as soon as an attributedText is defined.

hi,


(i think you were responding to my suggestion, not Claude31's on this, but we're both trying to help you moving forward.)


(and, i also note that as i'm about to post this, my good friend Claude31 has already added something -- so all of us will eventually get there.)


your code is much improved, and creating the Data struct for full quarterback information is the right thing to do -- it will make it easier to save and later restore all your data.


i'll make a suggestion, and then we can get on to the underlying question of saving data.


(1) as long as you're willing to always create an attributedString for every quarterback using a computed property of the Data struct, as i'll show below, and then factor out what's happening in your lengthy if statement, your cellForRowAtIndexPath code will get much less clunky, perhaps as this (if i am reading your if-then logic correctly):


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

let quarterback = objectsArray[indexPath.row]

cell.textLabel?.attributedText = quarterback.attributedText

cell.textLabel?.adjustsFontSizeToFitWidth = true

cell.textLabel?.font = UIFont.systemFont(ofSize: 22)

cell.accessoryType = quarterback.accessory ? .checkmark : .none

if !quarterback.color {

cell.backgroundColor = .none

} else if cell.accessory {

cell.backgroundColor = .systemGray3

} else {

cell.backgroundColor = .systemGray2

}

return cell

}


now, add some code for your Data struct:


struct Data {

var num: Int

var name: String

var strikeThrough: Bool = false

var color: Bool = false

var accessory: Bool = false


init(num: Int, name: String) {

self.num = num

self.name = name

}


var attributedText: NSAttributedString { // i had to do a quick EDIT on this line after my initial post

// rewrite as needed, since there appears to be some question about whether the number is included or not

let text = "\(num). \(name)"

let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: text)

let valueCode = strikeThrough ? 1 : 0

attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: valueCode, range: NSMakeRange(0, attributeString.length))

return attributeString

}


}


(2) since i added an init() method to your Data struct, you can now trim down some of the lengthy quarterBacks() function -- because all structs are initialized with the three booleans all false:


let objectsArray = [

Data(num: 1, name: "Patrick Mahomes, KC"),

Data(num: 2, name: "Deshaun Watson, HOU"),

...

Data(num: 30, name: "Ryan Fitzpatrick, MIA")

]



in the future, some code-cleaning suggestions are in order, and then getting to save data will be easy, because your Data struct is already Codable.




hope that helps,

DMG

Thank you.


I appreciate both of your help. This suggestion makes sense to write less code.

Yes it worked. Thank you. Now, how do I get a smooth animation? When I reload the table in the functions, it just instantly loads. I want a smooth animation.

Thank you for both of your help. I think I am ready to save the data and tableview. As I have mentioned, I cannot change views and go back to the view without the changed view back to its default view.

Hope you'll make it work.


If so, don't forget to close the thread.

hi,


i concur with Claude31 that we both hope you'll make this work!


but i'd also agree that it might be useful to close out this thread, if only because we're now 22 replies into it and still have not yet gotten to your thread's original question of "How do I save a table view?"


i'd suggest that once you really have the tableView working on its own -- the display is correct, you never call cellForRowAtIndexPath yourself, your swipe actions are working (making changes to the objectsArray directly), and the tableView is doing a reloadData() whenever the objectsArray is changed -- then it makes sense for the conversation to continue in a new thread, with working and updated code, possibly titled "How do I save a table view -- Part 2" or something.


i see three things from here:


(1) you added the question "Now, how do I get a smooth animation?" chances are the answer is to either call tableView.reloadRows(at:with:) to update an existing cell or cells individually, or to do a little more work when you add or delete rows. for example, to delete a player associated with an indexPath, you would call


top30QuarterbacksTable.beginUpdates()
  objectsArray.remove(at: indexPath.row)
  top30QuarterbacksTable.deleteRows(at: [indexPath], with: .fade)
top30QuarterbacksTable.endUpdates()


(2) you more recently added "I cannot change views and go back to the view without the changed view back to its default view." i'm not sure what this means -- is there another ViewController around that we don't know about? and is the Top_30_Quarterbacks UIViewController presented by another?


if so, that's another design issue to deal with, because right now a new objectsArray is created whenever time you bring the Top_30_Quarterbacks UIViewController on screen. it may or may not make sense in your application design for the Top_30_Quarterbacks UIViewController to read data from a file when it comes on screen, and then to write data when it goes off screen.


(3) and once the situation for (2) is clarified and we know "who owns the data," then we can address the more specific problem of how to persist data of any sort (e.g., your objectsArray, which as i said is already Codable and can be easily saved and restored using JSON).


we both look forward to where this goes!



hope that helps,

DMG