Search code examples
iosswiftcore-dataswift2nscalendar

How to handle empty objects with Swift?


I have an app that keeps track of monthly expenses. I have an Expense entity with a month attribute that keeps track of current month expense is created on. I would then display the expenses for each month in a table view as shown here. The user can only switch left and right only if there are expenses within the next or the last month

@IBAction func backMonthButtonPressed(sender: AnyObject) {
    print("Back Button Pressed")
    currentMonth = NSCalendar.currentCalendar().dateByAddingUnit(.Month, value: -1, toDate: currentMonth, options: [])!
    if checkMonth(currentMonth) {
        updateFetch()
        setMonth()
    } else {
        currentMonth = NSCalendar.currentCalendar().dateByAddingUnit(.Month, value: 1, toDate: currentMonth, options: [])!
    }
    tableView.reloadData()

}

@IBAction func nextMonthButtonPressed(sender: AnyObject) {

    print("Next Button Pressed")

    currentMonth = NSCalendar.currentCalendar().dateByAddingUnit(.Month, value: 1, toDate: currentMonth, options: [])!
    if checkMonth(currentMonth) {
        updateFetch()
        setMonth()
    } else {
        currentMonth = NSCalendar.currentCalendar().dateByAddingUnit(.Month, value: -1, toDate: currentMonth, options: [])!
    }
    tableView.reloadData()
}

func checkMonth(month : NSDate) -> Bool {
    let app = UIApplication.sharedApplication().delegate as! AppDelegate
    let context = app.managedObjectContext //scratch pad

    let fetchRequest = NSFetchRequest(entityName: "Expense")
    fetchRequest.predicate = NSPredicate(format: "month == %@", month.MonthYearDateFormatter())
    let count = context.countForFetchRequest(fetchRequest, error: nil)
    if count > 0 {
        print("There are expenses for this month \(month.MonthYearDateFormatter()). Show expenses")
        return true

    } else {
        print("There are no expenses for this month \(month.MonthYearDateFormatter()). Do Nothing")
        return false
    }
}

My problem is this, in the unlikely scenario that the user created an expense back in June and didn't create an expense in August. How can I let the user still see his/her expense back in August without skipping it. Any ideas?


Solution

  • I made some optimisation before elaboration:

    @IBAction func backMonthButtonPressed(sender: AnyObject) {
        self.processMonth(step: -1)
    }
    
    @IBAction func nextMonthButtonPressed(sender: AnyObject) {
        self.processMonth(step: 1)
    }
    
    // a method uses almost the same code for both cases, so it was merged
    func processMonth(step: Int) {
        let direction = (step < 1 ? "Back" : "Next")
        print("\(direction) Button Pressed")
    
        currentMonth = NSCalendar.currentCalendar().dateByAddingUnit(.Month, value: step, toDate: currentMonth, options: [])!
    
        //if checkMonth(currentMonth) {
        // I wouldn't test this because it locks you out from seeing empty month.
            updateFetch()
            setMonth()
        //}
    
        tableView.reloadData()
    }
    

    An answer to what you've exactly asked:

    If your data source for your UITableView is set properly, you should be able to go through empty months though

    // changed return type from `Bool` to `void` as I suggested in the method not to test empty month, as it could be useless
    
    func checkMonth(month : NSDate) {
        let app = UIApplication.sharedApplication().delegate as! AppDelegate
        let context = app.managedObjectContext //scratch pad
    
        let fetchRequest = NSFetchRequest(entityName: "Expense")
        fetchRequest.predicate = NSPredicate(format: "month == %@", month.MonthYearDateFormatter())
        let count = context.countForFetchRequest(fetchRequest, error: nil)
        if count > 0 {
            print("There are expenses for this month \(month.MonthYearDateFormatter()). Show expenses")
    
        } else {
            print("There are no expenses for this month \(month.MonthYearDateFormatter()). Do Nothing")
            // here you can make some additional actions, like seeting the empty table with "no expenses this month"
        }
    }
    

    Anyway, as @Paulw11 noted, if your data source is not size-exhausting, you could rather fetch the data from your data-model at viewDidLoad/viewDidAppear for example and then to render each month according to the currentMonth variable (regarding what month a user currently see).

    So as a result, you would call setMonth() method only in the load of your controller and each time a user changes a current month view.