Nicholas Piccoli
Nicholas Piccoli

Reputation: 188

UIPickerView + Core Data

UPDATE WITH ANSWER

import UIKit
import CoreData

class ExerciseViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPickerViewDataSource, UIPickerViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        VDL()

        //sets stepper configs
        setsStepper.wraps = false
        setsStepper.autorepeat = true
        setsStepper.continuous = true
        setsStepper.tintColor = UIColor.redColor()
        setsStepper.minimumValue = 0
        setsStepper.maximumValue = 500
        setsStepper.value = 0

        //reps stepper configs
        repsStepper.wraps = false
        repsStepper.autorepeat = true
        repsStepper.continuous = true
        repsStepper.tintColor = UIColor.orangeColor()
        repsStepper.minimumValue = 0
        repsStepper.maximumValue = 500
        repsStepper.value = 0

        exerciseTableView.reloadData()
    }

    var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext

    var fetchedResultsController: NSFetchedResultsController?

    @IBOutlet var exerciseTableView: UITableView!

    @IBOutlet var daysPickerView: UIPickerView!

    @IBOutlet var exerciseName: UITextField!
    @IBOutlet var setsStepper: UIStepper!
    @IBOutlet var repsStepper: UIStepper!

    @IBOutlet var setsNumber: UILabel!
    @IBOutlet var repsNumber: UILabel!

    var namesArray = [String]()
    var setsArray = [Int]()
    var repsArray = [Int]()
    var daysArray = [TrainingDay]()
    var detailsArray = [TrainingDetails]()

    func VDL () {
        let fetchRequest = NSFetchRequest(entityName: "TrainingDay")
        let sort = NSSortDescriptor(key: "dayIndex", ascending: true)
        fetchRequest.sortDescriptors = [sort]
        daysArray = (moc!.executeFetchRequest(fetchRequest, error: nil) as? [TrainingDay])!
        if daysArray.count == 0 { // nothing there
            let dayEntity = NSEntityDescription.entityForName("TrainingDay", inManagedObjectContext: moc!)
            let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
            for (index, name) in enumerate(days) {
                let newDay = TrainingDay(entity: dayEntity!, insertIntoManagedObjectContext: moc)
                newDay.day = name
                newDay.dayIndex = index
                daysArray.append(newDay)
            }
            var error: NSError?
            moc!.save(&error)
        }

    }


    func appendTrainingDetailsToArray () {
        let nameLabel = exerciseName.text
        namesArray.append(nameLabel)
        let numberOfSets = setsNumber.text?.toInt()
        setsArray.append(numberOfSets!)
        let numberOfReps = repsNumber.text?.toInt()
        repsArray.append(numberOfReps!)

        let row = daysPickerView.selectedRowInComponent(0)
        let currentDay = daysArray[row]


        let detailsEntity = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
        let trainingdetails = TrainingDetails(entity: detailsEntity!, insertIntoManagedObjectContext: moc)
        trainingdetails.trainingDay = currentDay

        var error: NSError?
        moc?.save(&error)

        if let err = error {
            var status = err.localizedFailureReason
            println("\(status)")
        } else {
            println("CURRENT SETTING: \(trainingdetails.trainingDay)")
        }
    }

    @IBAction func doneButton(sender: AnyObject) {
        appendTrainingDetailsToArray()

        exerciseTableView.reloadData()
    }

    @IBAction func setsStepperAction(sender: UIStepper) {
        println("\(Int(sender.value))")
        setsNumber.text = Int(sender.value).description
    }

    @IBAction func repsStepper(sender: UIStepper) {
        println("\(Int(sender.value))")
        repsNumber.text = Int(sender.value).description
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return namesArray.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cellIdentifier = "exerciseCell"
        var cell  = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
        if cell == nil {
            cell = UITableViewCell(style: UITableViewCellStyle.Value2, reuseIdentifier: cellIdentifier)
        }
        let row = indexPath.row
        println("\(row)")
        let details = detailsArray[indexPath.row]
        cell!.textLabel!.text = details.exerciseName
        cell?.detailTextLabel?.text = "Sets: #\(details.setsNumber) Reps: #\(details.repsNumber)"
        return cell!
    }

    //PICKER VIEW DELEGATE AND DATASOURCE METHODS
    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return daysArray.count
    }

    func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
            let trainingDay = daysArray[row]
            return trainingDay.day
    }

    func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

        let currentDay = daysArray[row]
        let fetchRequest = NSFetchRequest(entityName: "TrainingDetails")
        let predicate = NSPredicate(format: "trainingDay = %@", currentDay)
        fetchRequest.predicate = predicate
        let sort = NSSortDescriptor(key: "exerciseName", ascending: true)
        fetchRequest.sortDescriptors = [sort]
        detailsArray = (moc!.executeFetchRequest(fetchRequest, error: nil) as? [TrainingDetails])!
        exerciseTableView.reloadData()

    }
}

Previously, I asked help so I could adapt with UIPickerView. Like @pbasdf said, the code needed some adjustments to conform with core data and the picker view. You can see his explanation in his answer!

Upvotes: 1

Views: 1653

Answers (2)

Duncan Groenewald
Duncan Groenewald

Reputation: 8988

This is not going to compile and I may have mixed Swift and C# (!) but the general idea is shown below...

func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int 
{
   return trainingDays.Count;
}

func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! 
{
   return trainingDays[row].Name
}
// Variable for holding the selected day from the PickerView...
TrainingDay selectedTrainingDay;

// Set the selected TraininDay when the user choses a day from the PickerView
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) 
{
     selectedTrainingDay = trainingDays[row]

}

// Save the record
func Done() 
{
    // Create the trainingDetails
let detailsEntity = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
                        let trainingdetails = TrainingDetails(entity: detailsEntity!, insertIntoManagedObjectContext: moc)
                        trainingdetails.exerciseName = exerciseName.text
                        trainingdetails.setsNumber = setsNumber.text!
                        trainingdetails.repsNumber = repsNumber.text!
                        trainingdetails.trainingDay = selectedTrainingDay

                        var error: NSError?
                        moc?.save(&error)

}

// Array for holding the trainingDays
Array<TrainingDay> trainingDays = new Array<TrainingDay>()

// Create Core Data objects for each training day
// Call this once only or you will end up with multiple trainingDays
// So you need to do this the first time the app is run
// You can do a fetch on startup to see if they exist, and create them
// if they don't.
// Subsequent starts you will need to do a fetch to populate the 
// trainingDays array... Sorry no code for that here.
func createTrainingDays(moc:NSManagedObjectContext)
    {

        trainingDays.Add(createTrainingDay(moc, "TrainingDay", 0, "Sunday"));
        trainingDays.Add(createTrainingDay(moc, "TrainingDay", 1, "Monday"));
        trainingDays.Add(createTrainingDay(moc, "TrainingDay", 2, "Tuesday"));
        trainingDays.Add(createTrainingDay(moc, "TrainingDay", 3, "Wednesday"));
        trainingDays.Add(createTrainingDay(moc, "TrainingDay", 4, "Thursday"));
        trainingDays.Add(createTrainingDay(moc, "TrainingDay", 5, "Friday"));
        trainingDays.Add(createTrainingDay(moc, "TrainingDay", 6, "Saterday"));

        moc.save()
    }
// Helper Function
func createTrainingDay(moc:NSManagedObjectContext, entityName:String, sortIndex:Int, name:String) -> NSManagedObject
    {

        if let newManagedObject:NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entityName, inManagedObjectContext:moc) {

            newManagedObject.setValue(sortIndex, forKey:"sortIndex")
            newManagedObject.setValue(name, forKey:"name")

            return newManagedObject;

        }

    }

Upvotes: 0

pbasdf
pbasdf

Reputation: 21536

There are several things to address:

The TrainingDay entity

You've taken a wrong turn with the awakeFromInsert code. It doesn't insert new objects; it is run whenever you insert a new TrainingDay object. From what I can see in your code you never do actually insert new TrainingDays (which is lucky because the awakeFromInsert will crash - you are assigning an array of strings to a property which is a string).

You need instead to create 7 TrainingDay objects, one for each day of the week. Since these do not change, you can do this as a one-off task when your app is first run, or (as I do below) "lazily" when you try to fetch them. You do not need the awakeFromInsert code, but I would recommend adding another attribute to the entity, say "dayIndex", which will enable you to sort the TrainingDay objects into a logical order. Your code might look something like this:

// load the TrainingDays data...
let fetchRequest = NSFetchRequest(entityName: "TrainingDay")
let sort = NSSortDescriptor(key: "dayIndex", ascending: true)
fetchRequest.sortDescriptors = [sort]
daysArray = (moc!.executeFetchRequest(fetchRequest, error: nil) as? [TrainingDay])!
if daysArray.count == 0 { // nothing there
    let dayEntity = NSEntityDescription.entityForName("TrainingDay", inManagedObjectContext: moc!)
    let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
    for (index, name) in enumerate(days) {
        let newDay = TrainingDay(entity: dayEntity!, insertIntoManagedObjectContext: moc)
        newDay.day = name
        newDay.dayIndex = index
        daysArray.append(newDay)
    }
    moc.save(&error)
}

You could put this in viewDidLoad or in a func called from VDL. Then you can use daysArray as the data source for your picker view...

Populating the picker view

Abandon daysOfPickerView and use daysArray instead...

//PICKER VIEW DELEGATE AND DATASOURCE METHODS
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
    return 1
}

func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return daysArray.count
}

func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
    let trainingDay = daysArray[row]
    return trainingDay.day
}

Assigning the TrainingDetails object to the correct day

In the appendTrainingDetailsToArray method, you need to determine which day is currently selected in the picker view. For that purpose, you will need to add an @IBOutlet var for the pickerView and hook it up in your storyboard. Then you can access it and set the relationship for the TrainingDetails...

let row = pickerView.selectedRowInComponent(0)
let currentDay = daysArray[row]
trainingDetails.trainingDay = currentDay

Populating your table view Currently you are using three separate arrays. I would use just one:

var detailsArray = [TrainingDetails]()

Then use the properties of TrainingDetails to populate the table view cells, ie.

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cellIdentifier = "exerciseCell"
    var cell  = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
    if cell == nil {
        cell = UITableViewCell(style: UITableViewCellStyle.Value2, reuseIdentifier: cellIdentifier)
    }
    let row = indexPath.row
    let details = detailsArray[indexPath.row]
    cell!.textLabel!.text = details.exerciseName
    cell?.detailTextLabel?.text = "Sets: #\(details.setsNumber) Reps: #\(details.repsNumber)"
    return cell!
}

Amend the other table view data source methods likewise to use detailsArray.

Your three arrays are currently built up using the append... method, but I think what you want is to populate the table view with the correct 'TrainingDetails` for the chosen day...

Responding to the picker view changing

Currently your code seems to create new TrainingDetails every time the picker view changes. Instead, just fetch the TrainingDetails for the chosen day, and reload the table:

func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    let currentDay = daysArray[row]
    let fetchRequest = NSFetchRequest(entityName: "TrainingDetails")
    let predicate = NSPredicate(format: "trainingDay = %@", currentDay)
    fetchRequest.predicate = predicate
    let sort = NSSortDescriptor(key: "exerciseName", ascending: true)
    fetchRequest.sortDescriptors = [sort]
    detailsArray = (moc!.executeFetchRequest(fetchRequest, error: nil) as? [TrainingDetails])!
    exerciseTableView.reloadData()
}

Further thoughts

You have declared an NSFetchedResultsController but not used it. It would make sense to use it instead of the raw NSFetchRequest, because it will automatically respond to you adding new TrainingDetails and will add them to the table - rather than needing to use reloadData(). But that's for another day...

Also, if you'll forgive me, you're a bit liberal with optionals: ! and ?. I've left them in unchanged where I've copied your code, but you could tidy them up. Likewise with the error argument for the context operations - it's always worth using it. That said, I've drafted the above without testing it in Xcode, so forgive me if I've made the some mistakes in places; hopefully you can see the idea.

Upvotes: 1

Related Questions