Reem
Reem

Reputation: 267

Error on Reading from core data: ios swift

I'm parsing data from this data source and they are parsed correctly, then I'm saving data ( title, price and date) to CoreData, title is saved and read correctly, price and date are saved but when reading they are found nil. The error is "fatal error: unexpectedly found nil while unwrapping an Optional value" on returning result in function read. This is MyTableViewController.swift :

import UIKit
import CoreData

class MyTableViewController: UITableViewController {
@IBOutlet var songsTable: UITableView!
let viewModel = ViewModel()
var imageCache = [String:UIImage]()
var songs = [NSManagedObject]()
let firstDefaults = NSUserDefaults.standardUserDefaults()
let secondDefaults = NSUserDefaults.standardUserDefaults()


override func viewDidLoad() {
    super.viewDidLoad()
    self.refresh()
    self.refreshControl = UIRefreshControl()
    self.refreshControl?.addTarget(self, action: #selector(MyTableViewController.refresh), forControlEvents: .ValueChanged)

}

func refresh() {
    viewModel.fetch {
        dispatch_async(dispatch_get_main_queue()) {
            self.tableView.reloadData()
            self.refreshControl?.endRefreshing()
        }
    }


}


override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return self.viewModel.numberOfSections()
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.viewModel.numberOfItemsInSection(section)
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MyTableViewCell
    let integer: NSInteger = indexPath.row

    if !firstDefaults.boolForKey("titlesSaved") {
        saveItem(self.viewModel.titleForItemAtIndexPath(indexPath), id: integer, name: "title")
        firstDefaults.setBool(true, forKey: "titlesSaved")
    }
    cell.songTitle.text = read(integer,item: "title") as String

    //images are not displayed
    cell.songImage?.image = UIImage(named: "Blank52.png")
    let urlString = self.viewModel.imageForItemAtIndexPath(indexPath)

    let imgURL: NSURL = NSURL(string: urlString)!
    let request: NSURLRequest = NSURLRequest(URL: imgURL)
    NSURLConnection.sendAsynchronousRequest(
        request, queue: NSOperationQueue.mainQueue(),
        completionHandler: {(response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in
            if error == nil {
                cell.songImage.image = UIImage(data: data!)
            }
    })

    let thumbnailURLString = self.viewModel.imageForItemAtIndexPath(indexPath)
    let thumbnailURL = NSURL(string: thumbnailURLString)!

    //if image is cached
    if let img = imageCache[thumbnailURLString] {
        cell.songImage?.image = img
        print("image is cached")
    }
    else {
        // The image isn't cached, download the img data
        // We should perform this in a background thread
        let request: NSURLRequest = NSURLRequest(URL: thumbnailURL)
        let mainQueue = NSOperationQueue.mainQueue()
        NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in
            if error == nil {
                // Convert the downloaded data in to a UIImage object
                let image = UIImage(data: data!)
                // Store the image in to our cache
                self.imageCache[thumbnailURLString] = image
                // Update the cell
                dispatch_async(dispatch_get_main_queue(), {
                    if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) as! MyTableViewCell?{
                        cellToUpdate.songImage?.image = image
                    }
                })
            }
            else {
                print("Error: \(error!.localizedDescription)")
            }
        })
    }

    return cell
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if let detailsViewController: DetailsViewController = segue.destinationViewController as? DetailsViewController {
        let songIndex = songsTable!.indexPathForSelectedRow
        let integer: NSInteger = songIndex!.row

        //if !secondDefaults.boolForKey("pricesAndDatesSaved") {
            saveItem(self.viewModel.priceForItemAtIndexPath(songIndex!), id: integer, name: "price")
            saveItem(self.viewModel.dateForItemAtIndexPath(songIndex!), id: integer, name: "date")
            //secondDefaults.setBool(true, forKey: "pricesAndDatesSaved")
       // }
        detailsViewController.songPrice = read(integer,item: "price") as String
        detailsViewController.songDate = read(integer,item: "date") as String
       // detailsViewController.songPrice = self.viewModel.priceForItemAtIndexPath(songIndex!)
        //detailsViewController.songDate = self.viewModel.dateForItemAtIndexPath(songIndex!)

    }
}

func saveImage(id:Int,image:UIImage)
{
    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    let managedContext = appDelegate.managedObjectContext

    let entity = NSEntityDescription.entityForName("Song",
                                                   inManagedObjectContext: managedContext)
    let options = NSManagedObject(entity: entity!,
                                  insertIntoManagedObjectContext:managedContext)

    let newImageData = UIImageJPEGRepresentation(image,1)

    options.setValue(id, forKey: "index")
    options.setValue(newImageData, forKey: "image")


}

func saveItem(item:String, id:Int, name: String)
{
    print("my \(name) is \(item)")
    print("my id is \(id)")
    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    let managedContext = appDelegate.managedObjectContext

    let entity = NSEntityDescription.entityForName("Song",
                                                   inManagedObjectContext: managedContext)
    let options = NSManagedObject(entity: entity!,
                                  insertIntoManagedObjectContext:managedContext)

    options.setValue(id, forKey: "index")
    switch name {

    case "title":
        options.setValue(item, forKey: "title")
    case "price":
        options.setValue(item, forKey: "price")
    case "date":
        options.setValue(item, forKey: "date")

    default:
        print("error in switch")
    }


    do{
        try managedContext.save()

    } catch let error as NSError{
        print("could not save title \(error)")
    }

}

func read(id: Int, item:String)-> NSString
{
    print("my id in read func is \(id)")
    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    let managedContext = appDelegate.managedObjectContext
    let fetchRequest = NSFetchRequest(entityName: "Song")
    var result: NSString? = nil
    do {
        let results = try managedContext.executeFetchRequest(fetchRequest)
        let single_result = results[id]

        switch item {
        case "title":
            result = single_result.valueForKey("title") as? NSString
        case "price":
            result = single_result.valueForKey("price") as? NSString
        case "date":
            result = single_result.valueForKey("date") as? NSString

        default:
            print("error in switch in read function")
        }

            print ("i am reading the \(item): \(result)")
    }catch let error as NSError{
        print("could not fetch title \(error)")
    }
    return result!
}

And this is DetailsViewController.swift

import UIKit
import CoreData

class DetailsViewController: UIViewController {

@IBOutlet weak var price: UILabel!
@IBOutlet weak var releaseDate: UILabel!
var song: ViewModel?
var songPrice: String = ""
var songDate: String = ""

override func viewDidLoad() {
    super.viewDidLoad()
    price.text = "Price = \(self.songPrice)"
    print(self.songPrice)
    print(self.songDate)
    releaseDate.text = "Release date is: \(self.songDate)"
    // Do any additional setup after loading the view.
}

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

}

Upvotes: 0

Views: 290

Answers (1)

Marcus S. Zarra
Marcus S. Zarra

Reputation: 46718

You are doing the following:

  1. Call saveItem with title and ID
    1. Create new NSManagedObject
    2. Set title on new NSManagedObject
  2. Call saveItem with price and ID
    1. Create new NSManagedObject
    2. Set price on new NSManagedObject
  3. Call saveItem with date and ID
    1. Create new NSManagedObject
    2. Set date on new NSManagedObject

What you want to be doing is:

  1. Call saveItem with title and ID
    1. Create new NSManagedObject
    2. Set title on new NSManagedObject
  2. Call updateItem with price and ID
    1. FETCH existing object based on ID
    2. Set price on new NSManagedObject
  3. Call updateItem with date and ID
    1. FETCH existing object based on ID
    2. Set fate on new NSManagedObject

Or better yet:

  1. Call saveUpdateItem with title, price, date and ID
    1. Fetch item if it exists, otherwise create it
    2. Update item

You are getting a nil back because you are creating THREE objects instead of ONE object for each pass.

Upvotes: 1

Related Questions