Search code examples
iosswiftcore-datauipickerviewmbprogresshud

MBProgressHUD causing iOS application to crash (Swift 2.0)


I have a ViewController which saves user inputs to CoreData and after the save is attempted displaying MBProgressHUD to state if the save was successful or not.

I have an AddNewViewController class

class AddNewViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UITextFieldDelegate {

@IBOutlet weak var inputErrorMessage: UILabel!
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var amountLabel: UILabel!
    @IBOutlet weak var dayPicker: UIPickerView!
    @IBOutlet weak var durationPicker: UIPickerView!
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var amountTextField: UITextField!
    @IBOutlet weak var notesTextField: UITextField!

//variable to contrain the origin view controller
var originVC: String?

    // variables to hold user input
    var name: String?
    var amount: Double?
    var notes: String?
    var durationDay: Double?
    var durationType: String?

// The days and duration options to display in the pickers
    var durationPickerDataSource = ["Day(s)","Week(s)","Month(s)","Year(s)"];
    var dayPickerDataSource = ["1","2","3","4","5","6","7","8","9","10","11","12"];

@IBAction func saveButton(sender: AnyObject) {
        CoreDataStatic.data.saveIncomeBudgetAndExpenses(originVC!, name: name!, amount: amount, durationDay: durationDay!, durationType: durationType!, notes: notes!)
    }


/**
    The number of columns in the picker view.
    */
    func numberOfComponentsInPickerView(dayPickerView: UIPickerView) -> Int {
        return 1
    }

    /**
    The number of items in the picker view. Equal to the number of days(12) and duration options(4) .
    */
    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        if pickerView == durationPicker {
            return durationPickerDataSource.count;
        }
        else {
            return dayPickerDataSource.count;
        }
    }

    /**
    Gets the titles to use for each element of the picker view.
    */
    func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        if pickerView == durationPicker{
            durationType = durationPickerDataSource[row]
            return durationType
        }
        else {
            durationDay = Double(dayPickerDataSource[row])
            return dayPickerDataSource[row]
        }
    }
/**
    Display acknowledgement if the Income, Budget or Fixed Expense saved.
    */
    func displayMessage(origin: String) {
        var message : String

        //Changes the message depending on what the user was trying to save.
        if CoreDataStatic.data.saved == true {
        message = "\(origin) saved!"
                    }
        else if CoreDataStatic.data.saved == false {
            message = "Error: \(origin) failed to save!"
        }
        else {
            message = "Error!"
        }

        print(message)

        //displays acknowledgement for 2 seconds.
        /*let acknowledgement = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
        acknowledgement.mode = MBProgressHUDMode.Text
        acknowledgement.label.text = message
            acknowledgement.hideAnimated(true, afterDelay: 2)*/
    }

override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        self.durationPicker.dataSource = self;
        self.durationPicker.delegate = self;
        self.dayPicker.dataSource = self;
        self.dayPicker.delegate = self;
    }

A CoreData class:

struct CoreDataStatic {
    static let data = CoreData()
}

public class CoreData {

    var appDel : AppDelegate

    //Manage a collection of managed objects.
    let context : NSManagedObjectContext

    //Describes an entity in Core Data.
    let incomeEntity : NSEntityDescription
    let budgetEntity : NSEntityDescription
    let fixedExpenseEntity : NSEntityDescription

    //Retrieve data from Core Data with the entity 'Scores'.
    let income = NSFetchRequest(entityName: "Income")
    let budget = NSFetchRequest(entityName: "Budget")
    let fixedExpense = NSFetchRequest(entityName: "FixedExpenses")

    //Set the key that needs updating which is always 'score'
    let nameKeyToUpdate = "name"
    let amountDayKeyToUpdate = "amountDay"
    let amountWeekKeyToUpdate = "amountWeek"
    let amountMonthKeyToUpdate = "amountMonth"
    let amountYearKeyToUpdate = "amountYear"
    let durationDayKeyToUpdate = "durationDay"
    let durationTypeKeyToUpdate = "durationType"
    let notesKeyToUpdate = "notes"

    var saved : Bool?

func saveIncomeBudgetAndExpenses(origin: String, name: String, amountDay: Double, amountWeek: Double, amountMonth: Double, amountYear: Double, durationDay: Double, durationType: String, notes: String) {

        //saving in enity depending on origin view controller
        let entity : NSEntityDescription
        if origin == "Income" {
            entity = NSEntityDescription.entityForName("Income", inManagedObjectContext: context)!
        }
        else if origin == "Budget" {
            entity = NSEntityDescription.entityForName("Budget", inManagedObjectContext: context)!
        }
        else {
            entity = NSEntityDescription.entityForName("FixedExpenses", inManagedObjectContext: context)!
        }

        let saveNew = NSManagedObject(entity: entity,
            insertIntoManagedObjectContext:context)

        // add user input to the relevant entity
        saveNew.setValue(name, forKey: nameKeyToUpdate)
        saveNew.setValue(amountDay, forKey: amountDayKeyToUpdate)
        saveNew.setValue(amountWeek, forKey: amountWeekKeyToUpdate)
        saveNew.setValue(amountMonth, forKey: amountMonthKeyToUpdate)
        saveNew.setValue(amountYear, forKey: amountYearKeyToUpdate)
        saveNew.setValue(durationDay, forKey: durationDayKeyToUpdate)
        saveNew.setValue(durationType, forKey: durationTypeKeyToUpdate)
        saveNew.setValue(notes, forKey: notesKeyToUpdate)


    do {
    try context.save()
        print("saved")
        saved = true
    }
    catch _ {
        print("didnt save")
        saved = false
        }

        AddNewViewController().displayMessage(origin)
    }

    init(){
        appDel      = (UIApplication.sharedApplication().delegate as! AppDelegate)
        context     = appDel.managedObjectContext
        incomeEntity =  NSEntityDescription.entityForName("Income", inManagedObjectContext: context)!
        budgetEntity =  NSEntityDescription.entityForName("Budget", inManagedObjectContext: context)!
        fixedExpenseEntity =  NSEntityDescription.entityForName("FixedExpenses", inManagedObjectContext: context)!
    }
}

This code runs and as expected however when the commented out section in the displayMessage() function is uncommented I get the following error: "fatal error: unexpectedly found nil while unwrapping an Optional value"

due to the line self.durationPicker.dataSource = self; in the override viewDidLoad()

Any help would be appreciated.

Note* if i call the displayMessage() within the saveButton function the code works so unsure why it isn't working when calling the message from the CoreData class.


Solution

  • I am unsure if this is the correct way about it but i found a fix.

    a variable (bool) was created called attemptSave which is defaulted to false.

    within the saveIncomeBudgetAndExpenses try and catch, the attemptSave is changed to true.

    The displayMessage() function is now called within both button clicks using an if statement to check if an attemptSave is yes, if so, call function.