save checkmark on tableview using bool value of selected data model

Good Evening,


So I have a fitness app, the user selects one workout before it been displayed as a table view, when a cell is selected I want it to show that cell(containing an exercise) as completed by marking it with a checkmark. this works fine but I am struggling with how to save that check mark when the app is terminated and re launched.


So all my workouts conform to a struct containing an exercise(String) and completed(Bool) property. When the user clicks the cell and presents the check mark/ removes the checkmark i want the completed property to switch the bool in the model and save it as such.


There are currently around fifteen models/ fifteen workouts and I cant find a way of achieveing it.


I am fairly new to this so if any help could be offered I would be extremeley grateful!


Please can someone try and solve this!!


Thank you.

Josh

Replies

hi,


if the data you are saving are reasonably small (sounds like an array of strings/booleans), it would make sense to save this is UserDefaults. it's quick and simple.


but be sure to take a look at Paul Hudson's (hackingwithswift.com) video titled "How to save user settings using UserDefaults – Swift 5" it's available on YouTube.


in short, let's assume you have something like this:


struct Workout {
  var exercise: String
  var completed: Bool

  init(exercise: String, completed: Bool = false) {
    self.exercise = exercise
    self.completed = completed
  }
}

let defaultWorkoutList = [
  Workout(exercise: "Lifting"),
  Workout(exercise: "Running"),
  Workout(exercise: "Rowing"),
  Workout(exercise: "Jumping"),
  Workout(exercise: "Stretching")
]


you'll define a string as a name to identify the data you save in UserDefaults:


let kUserDefaultsKey = "WorkoutStatusKey"


when your app begins, or even just when your list of workouts comes on screen (viewWillAppear or even in viewDidLoad), get the data from UserDefaults


var workoutList = UserDefaults.standard.object(forKey: kUserDefaultsKey) as? [Workout] ?? defaultWorkoutList


anytime you make a change (toggling the completed boolean), be sure to add


UserDefaults.standard.setValue(workoutList, forKey: kUserDefaultsKey)


i think that should do it.



hope that helps,

DMG

Thanks for the reply, I'm new and still a little unsure on how to accomplish what you've described, can you try and assist me anymore?

Thank you.

Josh

If you want a answer which fits for your current code, you should better show your current code.

Especially, explaining how you display checkmarks, or how you organize your models.


You may not show all your fifteen models, but better show a few of them and all the code related to your table view.

Apologies,


The table VC is as follows:


import UIKit

import CoreData


class workoutTableView: UIViewController, UITableViewDataSource, UITableViewDelegate {


@IBOutlet weak var workoutTableView: UITableView!


var navTitle: String = ""

var workout = [Workout()]

let tlabel = UILabel()


override func viewDidLoad() {

super.viewDidLoad()


setWorkout()

workoutTableView.delegate = self

workoutTableView.dataSource = self

tlabel.text = navTitle

tlabel.textAlignment = .center

tlabel.font = UIFont(name: "Arial Rounded MT Bold", size: 30)

tlabel.adjustsFontSizeToFitWidth = true

navigationItem.titleView = tlabel

}


func setWorkout() {


if navTitle == "The 600 Workout" {


workout = The600Workout().workoutArray

}


else if navTitle == "5 Days for Muscle" {


workout = FiveDaysForMuscle().workoutArray


}


else if navTitle == "Marathon Ready" {


workout = MarathonReady().workoutArray

}


else if navTitle == "HIIT @ Home" {


workout = HIITAtHome().workoutArray

}


else if navTitle == "Get Strong" {


workout = GetStrong().workoutArray

}


else if navTitle == "Body Weight Blast" {


workout = BodyWeightBlast().workoutArray

}


else if navTitle == "Bands Pump" {


workout = BandsPump().workoutArray

}


else if navTitle == "******* Warm up" {


workout = QuickieWarmUp().workoutArray

}


else if navTitle == "The Best Circuit Workout" {


workout = TheBestCircuit().workoutArray

}


else if navTitle == "The Gym HIIT Workout" {


workout = GymHIIT().workoutArray

}


else if navTitle == "The Ultimate Workout" {


workout = UltimateWorkout().workoutArray

}



else if navTitle == "Warm up For Weights" {

workout = WarmUpForWeights().workoutArray

}


else if navTitle == "6 Day Bro Split" {


workout = SixDayBroSplit().workoutArray

}


else if navTitle == "Explosive Workout" {


workout = ExplosiveWorkout().workoutArray

}


else if navTitle == "Strength Circuit" {


workout = StrengthCircuit().workoutArray

}


else if navTitle == "Killer Circuit" {


workout = KillerCircuit().workoutArray

}


else if navTitle == "Fitness Test" {


workout = FitnessTest().workoutArray

}

}


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

return workout.count

}


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

workout[indexPath.row].completed = !workout[indexPath.row].completed

tableView.cellForRow(at: indexPath)?.accessoryType = workout[indexPath.row].completed ? .checkmark : .none

tableView.deselectRow(at: indexPath, animated: false)

}


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

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

cell.textLabel?.text = workout[indexPath.row].exercise

cell.accessoryType = workout[indexPath.row].completed ? .checkmark : .none

cell.layer.borderWidth = 5

cell.layer.cornerRadius = 20

cell.layer.borderColor = colorLiteral(red: 0, green: 0.3285208941, blue: 0.5748849511, alpha: 1)

cell.textLabel?.textColor = UIColor.black

cell.textLabel?.adjustsFontSizeToFitWidth = true

cell.textLabel?.font = .boldSystemFont(ofSize: 15)

return cell

}

}




and an example of one of the workout models:


import Foundation

import UIKit


class The600Workout {

var workoutArray = [

Workout(exercise: "Don't forget to warm up before every workout!", completed: false),

Workout(exercise: "Start with little/ no weight and work your way up", completed: false),

Workout(exercise: "------------------------------------------------------------------", completed: false),

Workout(exercise: "Pull ups | 25 Reps", completed: false),

Workout(exercise: "Lunges | 50 Reps (Low weight)", completed: false),

Workout(exercise: "Calf Raises | 50 Reps (Low weight)", completed: false),

Workout(exercise: "Shoulder press | 50 Reps (Low weight)", completed: false),

Workout(exercise: "Push ups | 50 Reps", completed: false),

Workout(exercise: "Shrugs | 50 Reps (Low weight)", completed: false),

Workout(exercise: "Leg raises | 50 Reps", completed: false),

Workout(exercise: "Bench press | 50 Reps (Low weight)", completed: false),

Workout(exercise: "More Pull ups | 25 Reps", completed: false),

Workout(exercise: "Squats | 50 Reps (Low weight)", completed: false),

Workout(exercise: "Incline Bench press | 50 Reps (Low weight)", completed: false),

Workout(exercise: "Bicep curls | 50 Reps (Low weight)", completed: false),

Workout(exercise: "Tricep pull downs | 50 Reps (Low weight)", completed: false), ]

}



The Workout() is just a struct with two properties(exercise and completed).



Any help would be greatly appreciated!


Thank you.


Josh

hi,


what your app does, really, is maintain several different lists of Workouts (each workout is a String and a Bool), according to different workout routines.


an obvious choice would be to store the list of Workouts for each workout routine in UserPreferences under its own key value. for example, you could store each list under its title -- except that would be a problem in case you starting changing the names of the workout routines (e.g., if you wanted to localize the names for a different language).


so i'd suggest keeping a master dictionary of workout routine names and the keys under which the associated list is stored in UserPreferences. the default setup below would work -- you can come back later to edit any title, as long as you leave the existing key alone.


let keyValueDictionary: [String : String] = [
  "600 Workout" : "600WorkoutKey",
  "5 Days for Muscle" : "5DayMuscleKey",
  ...
  "Fitness Test" : "FitnessTestKey"
]


when you do viewDidLoad(), go fetch the appropriate list by its keyValue from UserPreferences (or use the default list if it's never been used and saved before).


var workoutList = UserDefaults.standard.object(forKey: keyValueDictionary[navTitle]!) as? [Workout] ?? defaultWorkoutList(navTitle)

you'll have to write the function defaultWorkoutList(_ navTitle: String) -> [Workout] separately, but you have the bones of that already in the body of classes such as The600Workout and so on.


finally, whenever you make a change in a workout list (checking an exercise as done), write the list back to USerDefaults.


func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  workout[indexPath.row].completed = !workout[indexPath.row].completed
  tableView.deselectRow(at: indexPath, animated: false)
  tableView.reloadRows(at: [indexPath], with: .automatic) // EDITED AFTER THE FACT FOR .automatic
  UserDefaults.standard.setValue(workoutList, forKey: keyValueDictionary[navTitle]!)
}


that should do it.


hope that helps,

DMG