-1

I'm fairly new to swift and developing, I'm looking for some help with a problem I can't get past.

So essentially I have a bunch of custom class's which detail workouts, I use those workouts to populate the table view to show the user a list of exercises in the chosen particular workout.

I want to be able to place a checkmark next to an exercise within the table view once it has been completed, the issue I am having is the checkmark repeats when I scroll, I have now removed it for new cells but this causes the checkmark to go when I scroll down and then back up, I understand this is because I am reusing the cell. What I can't figure out is how to fix it, I have tried all sorts of solutions and none have worked.

Any advice would be much appreciated!

The code is below, Thank you!

p.s. Just for clarity, the navTitle is getting passed in from the previous VC.

import UIKit

class workoutTableView: UIViewController, UITableViewDataSource, UITableViewDelegate {


    var navTitle: String = ""
    var workout = [String]()
    let tlabel = UILabel()


    @IBOutlet weak var workoutTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        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 tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        if navTitle == "The 600 Workout" {

            workout = The600Workout().workoutArray
        }

        if navTitle == "5 Days for Muscle" {

            workout = FiveDaysForMuscle().workoutArray

        }

        if navTitle == "Marathon Ready" {

          workout = MarathonReady().workoutArray
        }

        if navTitle == "HIIT @ Home" {

          workout = HIITAtHome().workoutArray
        }

        if navTitle == "Get Strong" {

          workout = GetStrong().workoutArray
        }

        if navTitle == "Body Weight Blast" {

          workout = BodyWeightBlast().workoutArray
        }

        if navTitle == "Bands Pump" {

          workout = BandsPump().workoutArray
        }

        if navTitle == "Quickie Warm up" {

          workout = QuickieWarmUp().workoutArray
        }

        if navTitle == "The Best Circuit Workout" {

          workout = TheBestCircuit().workoutArray
        }

        if navTitle == "The Gym HIIT Workout" {

          workout = GymHIIT().workoutArray
        }

        if navTitle == "The Ultimate Workout" {

          workout = UltimateWorkout().workoutArray

        }


        if navTitle == "Warm up For Weights" {

            workout = WarmUpForWeights().workoutArray
        }

        if navTitle == "6 Day Bro Split" {

          workout = SixDayBroSplit().workoutArray
        }

        if navTitle == "Explosive Workout" {

         workout = ExplosiveWorkout().workoutArray
        }

        if navTitle == "Strength Circuit" {

          workout = StrengthCircuit().workoutArray
        }

        if navTitle == "Killer Circuit" {

          workout = KillerCircuit().workoutArray
        }

        if navTitle == "Fitness Test" {

          workout = FitnessTest().workoutArray
        }

        return workout.count

    }



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

        if tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark {
            tableView.cellForRow(at: indexPath)?.accessoryType = .none
        } else {
            tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
        }

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

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


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

        if navTitle == "The 600 Workout" {

            workout = The600Workout().workoutArray

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

        if navTitle == "5 Days for Muscle" {

            workout = FiveDaysForMuscle().workoutArray

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

        if navTitle == "Marathon Ready" {

            workout = MarathonReady().workoutArray

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

        if navTitle == "HIIT @ Home" {

            workout = HIITAtHome().workoutArray

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

        if navTitle == "Get Strong" {

            workout = GetStrong().workoutArray

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

        if navTitle == "Body Weight Blast" {

            workout = BodyWeightBlast().workoutArray

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

        if navTitle == "Bands Pump" {

            workout = BandsPump().workoutArray

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

        if navTitle == "Quickie Warm up" {

            workout = QuickieWarmUp().workoutArray

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

        if navTitle == "The Best Circuit Workout" {

            workout = TheBestCircuit().workoutArray

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

        if navTitle == "The Gym HIIT Workout" {

            workout = GymHIIT().workoutArray

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

        if navTitle == "The Ultimate Workout" {

            workout = UltimateWorkout().workoutArray

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

        if navTitle == "Warm up For Weights" {

            workout = WarmUpForWeights().workoutArray

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

        if navTitle == "6 Day Bro Split" {

            workout = SixDayBroSplit().workoutArray

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

        if navTitle == "Explosive Workout" {

            workout = ExplosiveWorkout().workoutArray

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

        if navTitle == "Strength Circuit" {

            workout = StrengthCircuit().workoutArray

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

        if navTitle == "Killer Circuit"  {

            workout = KillerCircuit().workoutArray

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

        if navTitle == "Fitness Test" {

            workout = FitnessTest().workoutArray

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

        cell.accessoryType = .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

    }

}
Parth
  • 2,682
  • 1
  • 20
  • 39
  • Create one boolean property in your model class, then change it whenever you want. Then set the checkmark based on your boolean property. If the property is true then cell.accessoryType = .checkmark, if it's false then cell.accessoryType = .none – Vinoth Vino Jan 06 '20 at 02:07
  • If `navTitle` is provided before the table view is loaded then no need to check the value of `navTitle` inside `cellForRow indexPath` and `numberOfRows` – u54r Jan 06 '20 at 02:15
  • Sadly I tried this and it didn't work, I think because it doesn't associate the bool with each cell. thank you anyway though – Joshwa Long Jan 06 '20 at 02:25
  • See Parth's example below to see one of the many ways to accomplish it. – u54r Jan 06 '20 at 04:33
  • Unrelated but your code is very, very expensive because all `if` conditions are checked even if `navTitle` matches the first string. Use the `if - else if` syntax or `switch`. Apart from that `cellForRow` and `numberOfRows` are the wrong places for those checks. – vadian Jan 06 '20 at 05:57

2 Answers2

3

First make a class/struct of Workout with a flag

struct Workout {
    let name: String
    let isComplete: Bool
}

Make a sample Data Model

var workouts = [
        Workout(name: "Squats", isComplete: false),
        Workout(name: "Burpees", isComplete: false),
        Workout(name: "Crunches", isComplete: true),
        Workout(name: "Push Ups", isComplete: false),
        Workout(name: "Jumping Jacks", isComplete: true),
        Workout(name: "High Knees", isComplete: false),
        Workout(name: "Lunges", isComplete: false),
        Workout(name: "Plank", isComplete: false),
        Workout(name: "Sechigh Knees", isComplete: false),
        Workout(name: "Tricep Dips", isComplete: false),
        Workout(name: "Mountain Climbers", isComplete: true),
        Workout(name: "Wall Sit", isComplete: true),
        Workout(name: "Squats 2", isComplete: false),
        Workout(name: "Burpees 2", isComplete: false),
        Workout(name: "Crunches 2", isComplete: true),
        Workout(name: "Push Ups 2", isComplete: false),
        Workout(name: "Jumping Jacks 2", isComplete: false),
        Workout(name: "High Knees 2", isComplete: false),
        Workout(name: "Lunges 2", isComplete: false),
        Workout(name: "Plank 2", isComplete: false),
        Workout(name: "Sechigh Knees 2", isComplete: true),
        Workout(name: "Tricep Dips 2", isComplete: false),
        Workout(name: "Mountain Climbers 2", isComplete: false),
        Workout(name: "Wall Sit 2", isComplete: false),
    ]

Make a custom UITableViewCell with a Workout type variable

class CustomCell: UITableViewCell {

    var workout: Workout? {
        didSet {
            guard let workout = workout else { return }

            self.textLabel?.text = workout.name

            if workout.isComplete {
                self.accessoryType = .checkmark
            }
            else {
                self.accessoryType = .none
            }
        }
    }
}

Then in tableView:cellForRowAtIndexPath: method pass the variable

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
        cell.workout = workouts[indexPath.row]

        return cell
    }
MBT
  • 1,381
  • 1
  • 6
  • 10
  • A custom cell is not needed if no custom UI elements are added. Set the `text` property and the `accessoryType` in `cellForRow`. – vadian Jan 06 '20 at 06:02
  • I have implemented this, with the exception of a custom cell, now I need to be able to save and load the check mark after the app is terminated. I am struggling with this and nobody seems to know the answer. Thank you. – Joshwa Long Jan 20 '20 at 02:10
  • You can save the data to UserDefaults. And fetch it when the app launch. Please check it - https://stackoverflow.com/questions/44876420/save-struct-to-userdefaults – MBT Jan 20 '20 at 03:53
-1

You will have to make an array of booleans of size equal to the length of the number of cells in the table view. Then whenever the state changes, you will have to update it.

Eg: If your table view has 5 exercises, then initially all 5 will be false since none of them has been completed.

var completed = [Bool](repeating: false, count: 5)

Then when the user taps on a cell, you will have to update this value as:

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

This way when you are rendering a cell, you can check if the workout was completed or not.

let cell = tableView.dequeueReusableCell(withIdentifier: "prototypeCell", for: indexPath)
cell.accessoryType = completed[indePath.row] ?  .checkmark : .none


I will update your code to include this and it should work for your current view controller. I have moved the code where you set the workout array to a separate code so you don't have to do the same work again. This should make your cellForRowAt a lot cleaner.
import UIKit

class workoutTableView: UIViewController, UITableViewDataSource, UITableViewDelegate {


    var navTitle: String = ""
    var workout = [String]()
    let tlabel = UILabel()

     //Keep track of completed state
     var completed: [Bool] = []

    @IBOutlet weak var workoutTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        //Setting the workout array in a separate function so don't have to do the check in number of rows
        setWorkout()
        //Initializing the completed array
        completed = [Bool](repeating: false, count: workout.count)

        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
        }

        if navTitle == "5 Days for Muscle" {

            workout = FiveDaysForMuscle().workoutArray

        }

        if navTitle == "Marathon Ready" {

          workout = MarathonReady().workoutArray
        }

        if navTitle == "HIIT @ Home" {

          workout = HIITAtHome().workoutArray
        }

        if navTitle == "Get Strong" {

          workout = GetStrong().workoutArray
        }

        if navTitle == "Body Weight Blast" {

          workout = BodyWeightBlast().workoutArray
        }

        if navTitle == "Bands Pump" {

          workout = BandsPump().workoutArray
        }

        if navTitle == "Quickie Warm up" {

          workout = QuickieWarmUp().workoutArray
        }

        if navTitle == "The Best Circuit Workout" {

          workout = TheBestCircuit().workoutArray
        }

        if navTitle == "The Gym HIIT Workout" {

          workout = GymHIIT().workoutArray
        }

        if navTitle == "The Ultimate Workout" {

          workout = UltimateWorkout().workoutArray

        }


        if navTitle == "Warm up For Weights" {

            workout = WarmUpForWeights().workoutArray
        }

        if navTitle == "6 Day Bro Split" {

          workout = SixDayBroSplit().workoutArray
        }

        if navTitle == "Explosive Workout" {

         workout = ExplosiveWorkout().workoutArray
        }

        if navTitle == "Strength Circuit" {

          workout = StrengthCircuit().workoutArray
        }

        if navTitle == "Killer Circuit" {

          workout = KillerCircuit().workoutArray
        }

        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) {
        //Updating the status of workout for current row in saved list
        completed[indexPath.row] = !completed[indexPath.row]
        tableView.cellForRow(at: indexPath)?.accessoryType = completed[indexPath.row] ?  .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]
        cell.accessoryType = completed[indexPath.row] ?  .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
    }
}

Parth
  • 2,682
  • 1
  • 20
  • 39
  • Would anyone be able to suggest how I'd persist the checkmark/ completed property. I have tried coreData and userDefaults but couldn't get it to work. Thank you. – Joshwa Long Jan 09 '20 at 02:43
  • What did you try? – Parth Jan 09 '20 at 02:44
  • I tried creating a core data model and fetching the data from there but there are issues with associating the completed state to its associated exercise, I also tried user defaults but faced the same issue. – Joshwa Long Jan 09 '20 at 03:20
  • You can try putting something like "exercise type" : "true,false,true,true" in user defaults. Then split the string on "," and then iterate through this array of strings and add true to your array if string value is "true". Kind of like converting the array into a string representation (serialization). – Parth Jan 09 '20 at 03:23
  • I’m sorry I don’t follow – Joshwa Long Jan 09 '20 at 15:11
  • Wait, I'll write a small code and add it to my answer when I get time. – Parth Jan 09 '20 at 15:59
  • Did you get chance to write some sample code? Thank you – Joshwa Long Jan 12 '20 at 16:06
  • No, not yet. It's been a busy week. I will do it soon hopefully. – Parth Jan 12 '20 at 17:10
  • Sorry to pester, have you had time yet? I’m really struggling to persist – Joshwa Long Jan 20 '20 at 09:50
  • Hey! Not yet. I will try to find some references for you today. And hopefully, I will get some time this week. Also, what method are you trying to persist? – Parth Jan 21 '20 at 15:09
  • Thank you so much! So I’ve changed the arrays to an array of Workout structs with an exercise (string) and completed(bool) property. I have got the bool changing depending on the cell been tapped but am struggling to persist it through user defaults, so when I close the app or even go back a page all the cells show their default value of false and no check mark shows. Thanks again – Joshwa Long Jan 21 '20 at 21:37
  • Hey! Sorry to keep bothering ya, have you had chance yet? Thank you – Joshwa Long Feb 10 '20 at 17:09
  • Hey, I am really sorry. It's been a very busy semester and I have a few midterms looming. I will get time to work on this is Feb last week. – Parth Feb 11 '20 at 03:54