dimanche 12 mars 2017

Swift 3 Xcode 8 UserDefaults Tableview to UIViewController

I just recently got help on here getting the state of checkbox buttons to persist in my UIViewController (either checked or unchecked with the corresponding png changing with the bool value of true or false and saved using UserDefaults).

However, because of the way the project is set up, the state of the checkboxes persists across every item in the UITableViewController that segues to its own UIViewController with the checkboxes in it. So when I click on one checkbox, it checks the same checkbox in every UIViewController in the UITableViewController.

I'm not sure how to fix this as the CurrentGoalViewController (the one with the checkboxes) is segued by a dynamic tableview. Any help would be appreciated, thanks. Below is code for the corresponding views.

CurrentGoalsTableViewController (The tableview with all the goals listed)

import UIKit

class CurrentGoalsTableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Current Goals"

        //Setup a notification to let us know when the app is about to close, and that we should store the user items to persistence.
        //This will call the applicationDidEnterBackground() function in
        //this class
        NotificationCenter.default.addObserver(self, selector: #selector(UIApplicationDelegate.applicationDidEnterBackground(_:)), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)

        do
        {
            //Try to load goalitems from persistence
            goals = try[Goal].readFromPersistence() //read values saved for goal text 
        }
        catch let error as NSError //catch errors
        {
            if error.domain == NSCocoaErrorDomain && error.code == NSFileReadNoSuchFileError
            {
                NSLog("No persistence file found, not necessarily an error")
            }
            else
            {
                let alert = UIAlertController(
                    title: "Error",
                    message: "Could not load the goal items",
                    preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)

                NSLog("Error loading from persistence: \(error)")
            }
        }

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem()
    }

     private var goalitems = [Goal]()

    @objc
    public func applicationDidEnterBackground(_ notification: NSNotification)
    {
        do
        {
            try goals.writeToPersistence() //save goalitems to persistence
        }
        catch let error
        {
            NSLog("Error writing to persistence: \(error)")
        }
    }

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

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        tableView.reloadData() //reload data every time we come to this view
        self.tabBarController?.navigationItem.title = "Current Goals"
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return goals.count //count is amount of goalitems
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let currentGoal = goals[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "basic", for: indexPath)
        cell.textLabel?.text = currentGoal.name 
        // Configure the cell...

        return cell //for each goalitem, return cell with goal name
    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if indexPath.row < goals.count {
            goals.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .top)
        }
    }

    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.

        if let currentGoalViewController = segue.destination as? CurrentGoalViewController {
            if let indexPath = self.tableView.indexPathForSelectedRow {
                currentGoalViewController.currentGoal = goals[indexPath.row]
            } //segue from the clicked cell goalitem in CurrentGoalsTableViewController to the detail view of that item in CurrentGoalViewController
        }
    }
}

CurrentGoalViewController (for the views with the checkboxes)

import UIKit

let defaults = UserDefaults.standard

class CurrentGoalViewController: UIViewController {

var currentGoal: Goal?

var uncheckedBox = UIImage(named: "checkbox") //unchecked checkbox png
var checkedBox = UIImage(named: "checkedbox") //checked checkbox png

var isboxclicked: Bool! 
var isbox2clicked: Bool!
var isbox3clicked: Bool!

@IBOutlet weak var progressView: UIProgressView!

var checkedBoxTotal = 0

@IBOutlet weak var goalCompletedLabel: UILabel! 
@IBOutlet weak var deadlineLabel: UILabel!

@IBOutlet weak var uncheckBox: UIButton! //one button for each goal
@IBOutlet weak var uncheckBox2: UIButton!
@IBOutlet weak var uncheckBox3: UIButton!

private var checkMarkItems = [CheckmarkItem]()

@IBAction func clickBox(_ sender: UIButton) {
    if isboxclicked == true {
        isboxclicked = false  //if box is checked, when you click on it it unchecks
        defaults.set(isboxclicked, forKey:"checkboxstatus") //set bool for UserDefaults
        uncheckBox.setImage(uncheckedBox, for: UIControlState.normal) //set image of checkbox to unchecked image
        checkedBoxTotal = checkedBoxTotal - 1 //Amount of checkboxes checked is one less
        defaults.set(checkedBoxTotal, forKey: "checkboxtotal") //save amount of checkbox total

        showCompleteLabel()
        progressView.progress = Float(checkedBoxTotal)/Float(3) 

        defaults.set(progressView.progress, forKey:"progressviewprogress")
    } else {
        isboxclicked = true //if not checked, check it
        defaults.set(isboxclicked, forKey: "checkboxstatus")
        uncheckBox.setImage(checkedBox, for:UIControlState.normal)
        checkedBoxTotal = checkedBoxTotal + 1
        defaults.set(checkedBoxTotal, forKey: "checkboxtotal")
        progressView.progress = Float(checkedBoxTotal)/Float(3)
        showCompleteLabel()
        defaults.set(progressView.progress, forKey:"progressviewprogress")
        }
}

@IBAction func clickBox2(_ sender: UIButton) { //second checkbox, same logic as first
    if isbox2clicked == true {
        isbox2clicked = false

        sender.setImage(#imageLiteral(resourceName: "checkbox"), for: UIControlState.normal)
        defaults.set(isbox2clicked, forKey: "checkboxstatus2")
        checkedBoxTotal = checkedBoxTotal - 1
        defaults.set(checkedBoxTotal, forKey: "checkboxtotal")

        progressView.progress = Float(checkedBoxTotal)/Float(3)
        showCompleteLabel()

        defaults.set(progressView.progress, forKey:"progressviewprogress")
    } else {
        isbox2clicked = true
        defaults.set(isbox2clicked, forKey: "checkboxstatus2")

        sender.setImage(#imageLiteral(resourceName: "checkedbox"), for:UIControlState.normal)
        checkedBoxTotal = checkedBoxTotal + 1

        defaults.set(checkedBoxTotal, forKey: "checkboxtotal")

        progressView.progress = Float(checkedBoxTotal)/Float(3)
        showCompleteLabel()

        defaults.set(progressView.progress, forKey:"progressviewprogress")
    }
}

@IBAction func clickBox3(_ sender: UIButton) { //third checkbox, same logic
    if isbox3clicked == true {
        isbox3clicked = false
        sender.setImage(#imageLiteral(resourceName: "checkbox"), for: UIControlState.normal)
        defaults.set(isbox3clicked, forKey: "checkboxstatus3")
        checkedBoxTotal = checkedBoxTotal - 1
        defaults.set(checkedBoxTotal, forKey: "checkboxtotal")
        progressView.progress = Float(checkedBoxTotal)/Float(3)
        showCompleteLabel()
        defaults.set(progressView.progress, forKey:"progressviewprogress")

    } else {

        isbox3clicked = true
        sender.setImage(#imageLiteral(resourceName: "checkedbox"), for:UIControlState.normal)

        defaults.set(isbox3clicked, forKey: "checkboxstatus3")
        checkedBoxTotal = checkedBoxTotal + 1

        defaults.set(checkedBoxTotal, forKey: "checkboxtotal")

        progressView.progress = Float(checkedBoxTotal)/Float(3)
        showCompleteLabel()

        defaults.set(progressView.progress, forKey:"progressviewprogress")              
    }
}

func showCompleteLabel() { 
    if checkedBoxTotal == 3 { //if amount of boxes checked is 3, show goal complete label
        goalCompletedLabel.isHidden = false
    }
    else { //otherwise, hide it
        goalCompletedLabel.isHidden = true
    }
}

@IBOutlet weak var currentGoalNameLabel: UILabel!

@IBOutlet weak var goalPoint1Label: UILabel!

@IBOutlet weak var goalPoint2Label: UILabel!

@IBOutlet weak var goalPoint3Label: UILabel!


override func viewDidLoad() {
    super.viewDidLoad()

    self.title = "Current Goal"
    //boxes are initially unchecked
    isboxclicked = false 
    isbox2clicked = false
    isbox3clicked = false

    goalCompletedLabel.isHidden = true //goal completed label originally hidden
}

override func viewWillAppear(_ animated: Bool) {
    //loading state of checkbox
    isboxclicked = defaults.bool(forKey: "checkboxstatus")
    //if state is clicked, set checkbox to checked image
    if isboxclicked == true {
        uncheckBox.setImage(checkedBox, for: .normal)
    }
    //same logic as checkbox one
    isbox2clicked = defaults.bool(forKey: "checkboxstatus2")
    if isbox2clicked == true {
        uncheckBox2.setImage(checkedBox, for: .normal)
    }
    //same logic as checkbox two
    isbox3clicked = defaults.bool(forKey: "checkboxstatus3")
    if isbox3clicked == true {
        uncheckBox3.setImage(checkedBox, for: .normal)
    }
    //amount of checkboxes checked loaded
    checkedBoxTotal = defaults.integer(forKey: "checkboxtotal")
    //progress value loaded
    progressView.progress = defaults.float(forKey: "progressviewprogress")

    if let g = currentGoal { //text label values loaded into current CurrentGoalViewController 

        currentGoalNameLabel.text = g.name
        goalPoint1Label.text = g.goalPoint1
        goalPoint2Label.text = g.goalPoint2
        goalPoint3Label.text = g.goalPoint3
        deadlineLabel.text = g.deadline
    }
}

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


/*
// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
}
*/
}

Goals class (for the text labels)

    import Foundation

    var goals = [Goal]()

    class Goal: NSObject, NSCoding {

        var name: String
        var goalPoint1: String
        var goalPoint2: String
        var goalPoint3: String
        var deadline: String

        init(name: String, goalPoint1: String, goalPoint2: String, goalPoint3: String, deadline: String)
        {
            self.name = name
            self.goalPoint1 = goalPoint1
            self.goalPoint2 = goalPoint2
            self.goalPoint3 = goalPoint3
            self.deadline = deadline
        }

        required init?(coder aDecoder: NSCoder)
        {
            if let name = aDecoder.decodeObject(forKey: "name") as? String {
                self.name = name
            }
            else {
                return nil
            }

            if let goalPoint1 = aDecoder.decodeObject(forKey: "goalPoint1") as? String {
                self.goalPoint1 = goalPoint1
            }
            else {
                return nil
            }

            if let goalPoint2 = aDecoder.decodeObject(forKey: "goalPoint2") as? String {
                self.goalPoint2 = goalPoint2
            }
            else {
                return nil
            }

            if let goalPoint3 = aDecoder.decodeObject(forKey: "goalPoint3") as? String {
                self.goalPoint3 = goalPoint3
            }
            else {
                return nil
            }

            if let deadline = aDecoder.decodeObject(forKey: "deadline") as? String {
                self.deadline = deadline
            }
            else {
                return nil
            }
        }

        func encode(with aCoder: NSCoder) {
            aCoder.encode(self.name, forKey: "name")
            aCoder.encode(self.goalPoint1, forKey: "goalPoint1")
            aCoder.encode(self.goalPoint2, forKey: "goalPoint2")
            aCoder.encode(self.goalPoint3, forKey: "goalPoint3")
            aCoder.encode(self.deadline, forKey: "deadline")
        }
    }

    //Creates an extension of the Collection type (aka an Array),
    //but only if it is an array of Goal objects
    extension Collection where Iterator.Element == Goal
    {
        //Builds the persistence URL. This is a location inside the
        //"Application Support" directory for the App.
        private static func persistencePath() -> URL?
        {
            let url = try? FileManager.default.url(
                for: .applicationSupportDirectory,
                in: .userDomainMask,
                appropriateFor: nil,
                create: true)

            return url?.appendingPathComponent("goalitems.bin")
        }

        //Write the array to persistence
        func writeToPersistence() throws
        {
        if let url = Self.persistencePath(), let array = self as? NSArray
        {
        let data = NSKeyedArchiver.archivedData(withRootObject: array)
            try data.write(to: url)
        }
        else {
            throw NSError(domain: "com.example.Goal", code: 10 , userInfo: nil)
            }
        }

        //Read the array from persistence
        static func readFromPersistence() throws -> [Goal]
        {
            if let url = persistencePath(), let data = (try Data(contentsOf: url) as Data?)
            {
                if let array = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Goal]
                {
                    return array
                }
                else
                {
                    throw NSError(domain: "com.example.Goal", code: 11, userInfo: nil)
                }
            }
            else
            {
                throw NSError(domain: "com.example.Goal", code: 12, userInfo: nil)       
            }
        }
    }




Aucun commentaire:

Enregistrer un commentaire