Fetching the most recent item from CoreData in Swift - core-data

I'm new to CoreData, have read a few tutorials but have come up empty-handed. Most tutorials have centered around fetching from CoreData to populate a tableview, which is not what I'm doing at the moment.
Here's how I'm setting the data in CoreData. All types are Double except for date which is an NSDate
let appDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let managedObjectContext = appDelegate.managedObjectContext
let entityDescription = NSEntityDescription.entityForName("Meditation", inManagedObjectContext: managedObjectContext!)
let meditation = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: managedObjectContext!)
meditation.setValue(settings.minutes, forKey: "length")
meditation.setValue(settings.warmup, forKey: "warmup")
meditation.setValue(settings.cooldown, forKey: "cooldown")
meditation.setValue(NSDate(), forKey: "date")
// fetch stuff from CoreData
var request = NSFetchRequest(entityName: "Meditation")
var error:NSError? = nil
var results:NSArray = managedObjectContext!.executeFetchRequest(request, error: &error)!
for res in results {
println(res)
}
Here's what I'm trying to do to get the results, but I'm not able to access things like .minutes, .date, etc. I know I'm also not properly getting the last item either, I was just trying to print out attributes on the object first.
I'd love help on how to fetch only the most recent object as well as show it's attributes
Thanks!

First, create a "Meditation.swift" file in Xcode with "Editor -> Create NSManagedObject
Subclass ...". The generated file should look like
import Foundation
import CoreData
class Meditation: NSManagedObject {
#NSManaged var length: NSNumber
#NSManaged var warmup: NSNumber
#NSManaged var cooldown: NSNumber
#NSManaged var date: NSDate
}
Now you can use the properties directly instead of Key-Value Coding
and create the object as
let meditation = NSEntityDescription.insertNewObjectForEntityForName("Meditation", inManagedObjectContext: managedObjectContext) as Meditation
meditation.length = ...
meditation.warmup = ...
meditation.cooldown = ...
meditation.date = NSDate()
var error : NSError?
if !managedObjectContext.save(&error) {
println("save failed: \(error?.localizedDescription)")
}
When fetching the object, cast the result of
executeFetchRequest() to [Meditation]:
let request = NSFetchRequest(entityName: "Meditation")
var error : NSError?
let result = managedObjectContext.executeFetchRequest(request, error: &error)
if let objects = result as? [Meditation] {
for meditation in objects {
println(meditation.length)
println(meditation.date)
// ...
}
} else {
println("fetch failed: \(error?.localizedDescription)")
}
Finally, to fetch only the latest object, add a sort descriptor to
sort the results by date in descending order, and limit the number of results to one:
let request = NSFetchRequest(entityName: "Meditation")
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
request.fetchLimit = 1
// ...
Then the objects array contains at most one object, which is the most recent one.

Related

CoreData insertNewObjectForEntityForName Causing Crash

I've been chasing a bug for a while now and I can't figure it out. I have a class that takes a bunch of parsed data and then calls a method to create new core data "Article" object for each element created from the parsed data. I've shown how I declare the NSManagedObjectContext below.
You will also see the method
Article.createFLOArticleWithStructure(element, inManagedObjectContext: self.articleContext)
I placed this method in an extension to clean up the code. The method is show below.
import Foundation
class FLODataHandler : NSObject, FLOConnectionHandlerDelegate, FLOParserHandlerDelegate, TriathleteParserHandlerDelegate, VeloNewsParserHandlerDelegate, CyclingNewsParserHandlerDelegate, RoadBikeActionParserHandlerDelegate, IronmanParserHandlerDelegate
{
let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
lazy var articleContext: NSManagedObjectContext = self.appDelegate.managedObjectContext!
func floParserHandlerDidFinishParsing(parserHandler : FLOParserHandler)
{
for element in self.floParserHandler!.XMLParsedDataArray!
{
// The pubDate, tilte and link and indicator have been added to the titleLinkArray. I will now add the data to a Core Data Entity
// in the Article+NewsFeedArticle class.
Article.createFLOArticleWithStructure(element, inManagedObjectContext: self.articleContext)
}
}
Extension Code
extension Article
{
class func createFLOArticleWithStructure(articleStructure: DateTitleLink, inManagedObjectContext context: NSManagedObjectContext) -> (Article)
{
// Core data is much simpler in Swift. I have not commented this code since I do not know if it's working yet.
var article = Article()
//var entity = NSEntityDescription("Article", inManagedObjectContext: context)
let fetchRequest = NSFetchRequest(entityName: "Article")
let predicate = NSPredicate(format: "feed == 'FLO Cycling' AND title == %#", articleStructure.title!)
let sortDescriptor = NSSortDescriptor(key: "pubDate", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Set up NSError
var fetchError : NSError?
// When you perform a fetch the returned object is an array of the Atricle Entities.
let fetchedObjects = context.executeFetchRequest(fetchRequest, error: &fetchError) as! [Article]
if fetchedObjects.count > 1
{
println("Error! in Article+NewFeedArticle.swift")
}
else if fetchedObjects.count == 0
{
article = NSEntityDescription.insertNewObjectForEntityForName("Article", inManagedObjectContext: context) as! Article
article.feed = "FLO Cycling"
article.pubDate = articleStructure.date!
article.title = articleStructure.title!
article.link = articleStructure.link!
article.theNewArticle = NSNumber(int: 1)
var error : NSError?
if(context.save(&error))
{
println(error!.localizedDescription)
}
}
else if fetchedObjects.count == 1
{
article = fetchedObjects[fetchedObjects.endIndex - 1]
}
return article
}
When I run the code I get stopped on the following line of the extension code and receive the following errors.
article = NSEntityDescription.insertNewObjectForEntityForName("Article", inManagedObjectContext: context) as! Article
CoreData: error: Failed to call designated initializer on
NSManagedObject class 'FLOCycling1_1_1.Article' CoreData: warning:
Unable to load class named 'Article' for entity 'Article'. Class not
found, using default NSManagedObject instead. Could not cast value of
type 'NSManagedObject_Article_' (0x7fe59c3515e0) to
'FLOCycling1_1_1.Article' (0x106139f70).
I've read online about using a prefix in the data model. I've added this to no avail. If you have any idea how I can fix this error I would appreciate the help.
ADDED****
Here is the Article.swift file on request.
import Foundation
import CoreData
#objc class Article: NSManagedObject
{
#NSManaged var feed: String
#NSManaged var link: String
#NSManaged var pubDate: NSDate
#NSManaged var theNewArticle: NSNumber
#NSManaged var title: String
}
Take care,
Jon
The issue had to do with my declaration of Article in this line here.
var article = Article()
In the Swift version calling the line of code above allocates and initializes memory for the Article. In the Objective-C version of my code I called the following.
Article *article = nil;
While I am not sure why, allocating and initializing the Article is the issue. I found this post here about a similar error.
Failed to call designated initializer on NSManagedObject class 'ClassName'
To fix this I changed the code to
var article = Article?()
To be clear, I also changed the Article class to ProjectName.Article.
I hope this helps someone else.
Take care,
Jon

Swift update existing item core data

I have seen many examples with one view controller for adding or updating core data items. Any thoughts on pros or cons of doing in separate view controllers?
My code for trying to do the update I think I am missing one key part to get it to work.
#IBAction func saveItem(sender: AnyObject) {
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Items", inManagedObjectContext: context)
var existingItem = dataModel.self
if (row > 0) {
println(teaname.text)
existingItem.setValue(teaname.text as String, forKey: "name")
existingItem.setValue(teatype.text as String, forKey: "type")
existingItem.setValue(qty.text as String, forKey: "amount")
existingItem.setValue(temp.text as String, forKey: "temp")
existingItem.setValue(time.text as String, forKey: "time")
} else {
}
context.save(nil)
self.navigationController?.popViewControllerAnimated(true)
}
I get (lldb) with a thread breakpoint at existingItem.setValue(teaname.text as String, forKey: "name")
It does not appear you actually have a specific object to update. I use the following function to fetch an object by its unique ID. Only once you have an object (mine is called Event) can you update it.
func fetchEvent(eventID: Int) -> Event? {
// Define fetch request/predicate/sort descriptors
var fetchRequest = NSFetchRequest(entityName: "Event")
let sortSections = NSSortDescriptor(key: "eTitle", ascending: true)
let sortDescriptor = NSSortDescriptor(key: "eID", ascending: true)
let predicate = NSPredicate(format: "eID == \(eventID)", argumentArray: nil)
var error = NSErrorPointer()
// Assign fetch request properties
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [sortSections, sortDescriptor]
fetchRequest.fetchBatchSize = 1
fetchRequest.fetchLimit = 1
// Handle results
let fetchedResults = managedObjectContext?.executeFetchRequest(fetchRequest, error: error)
if fetchedResults?.count != 0 {
if let fetchedEvent: Event = fetchedResults![0] as? Event {
println("Fetched object with ID = \(eventID). The title of this object is '\(fetchedEvent.eTitle)'")
return fetchedEvent
}
}
return nil
}
Once you have fetched an object and have a core data object to update, then you can update it like so.
func updateEvent(eventDict: Dictionary<String, AnyObject>, id: Int) {
if let event: Event = fetchEvent(id) {
println(event)
event.eID = id
event.eTitle = getString(eventDict["title"])
event.eLocation = getString(eventDict["location"])
event.eDescription = getString(eventDict["description"])
event.eStart = getDate(eventDict["startDate"])
event.eEnd = getDate(eventDict["endDate"])
event.eMod = NSDate()
event.eSecID = getSecID(event)
}
}
And then you may want to save your managed object context.

SWIFT - 'AnyObject' does not have a member name 'fooBar'

I am trying to fetch some data from Core Data and have run into a slight problem. I can fetch the data with no problem. The moment I try to grab a specific piece of data (i.e. data.fooBar), it throws up an error:
"'AnyObject' does not have a member name 'fooBar'
If I println(data) it will show that fooBar does exist with data stored in it.
I am not really sure why it is doing this. I have tried to search for an answer and tried a bunch of different things but none have seemed to work. Any help would be great. Thanks. :)
var results : Array<AnyObject> = []
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
//get the data for that storedItem
var appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
var context:NSManagedObjectContext = appDel.managedObjectContext!
let req = NSFetchRequest(entityName: "storedItems")
let name:String = results[indexPath.row].name
req.predicate = NSPredicate(format: "name == %#", name)
req.returnsObjectsAsFaults = false
var tapResults = context.executeFetchRequest(req, error: nil)!
for item in tapResults {
println(item) //works, shows all data correctly(including subText)
println(item.name) //works, the only one that does for some reason???
println(item.subText) //Error 'AnyObject' does not have a member name 'subText'
}
Here is the result for: println(item)
println(item) <NSManagedObject: 0x7f04be60> (entity: storedItems; id: 0x7f041de0 <x-coredata://DD4F8E68-2234-46B5-B1D8-AE2F75245C63/storedItems/p1> ; data: {
alarmSound = default;
isDefault = 0;
name = "test";
sliderHours = 0;
sliderMinutes = 0;
sliderSeconds = 0;
subText = "00:00:00";
UPDATE: Based on discussion over vacawama answer (Thank you Aaron). For correct solution please see the answer I accepted.
my itemObj class
#objc(itemObj)
class itemObj: NSManagedObject {
#NSManaged var name:String!
#NSManaged var sliderHours:NSNumber
#NSManaged var sliderMinutes:NSNumber
#NSManaged var sliderSeconds:NSNumber
#NSManaged var subText:String!
#NSManaged var alarmSound:String!
#NSManaged var isDefault:NSNumber
}
my AddItem VC:
var tResults = (context.executeFetchRequest(req, error: nil))
for item in tResults as [itemObj!] {
println(item.name)
println(item.subText)
}
executeFetchRequest returns an optional array of AnyObject. You shouldn't force-unwrap it (this can cause a crash). So optionally unwrap it and do an optional cast (as?) to make sure the type is correct:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let req = NSFetchRequest(entityName: "storedItems")
let name:String = results[indexPath.row].name
req.predicate = NSPredicate(format: "name == %#", name)
req.returnsObjectsAsFaults = false
let tapResults = context.executeFetchRequest(req, error: nil)
if let presentResults = tapResults {
if let castedResults = presentResults as? [MyManagedObjectSubclass] {
for item in castedResults {
println(item)
println(item.name)
println(item.subText)
}
}
}
}
I also changed all of your vars to lets since they don't need to be mutable.
Just replace MyManagedObjectSubclass with whatever your NSManagedObject subclass is.

swift core data fetching relationship

I'm a little bit confused try to fetch relation data from coredata in swift
Person Entity
contains the name of person and Unique Id. The relation with Books is ONE to Many
For example
Person Entity:
idPerson = 25 - namePerson = John
idPerson = 26 - namePerson = Steve
Books Entity contains the title of books, Unique Id for the book and a relation ID with person (personBook). The relation with Person is ONE to ONE
For example
Books Entity:
idBook = 2543 - titleBook = title one - personBook = 25
idBook = 2544 - titleBook = title two - personBook = 25
idBook = 2545 - titleBook = title three - personBook = 26
here my data model screenshot: (no image because i have no reputation)
Person class
#objc(Person)
class Person: NSManagedObject {
#NSManaged var idPerson: String
#NSManaged var namePerson: String
#NSManaged var booksRel: NSSet
}
Books class
#objc(Books)
class Books: NSManagedObject {
#NSManaged var bookTitle: String
#NSManaged var idBook: String
#NSManaged var personBook: String
#NSManaged var personRel: Person
}
Fetch code
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let request = NSFetchRequest(entityName: "Books")
request.returnsObjectsAsFaults = false
request.fetchLimit = 30
////////////////////////////
// CODE TO JOIN Person entity WHERE personBook = idPerson
////////////////////////////
var results:NSArray = context.executeFetchRequest(request, error: nil)!
for temp in results {
var data = temp as Books
///////////////////////////////////////
//println(data.namePerson) ----> not working
///////////////////////////////////////
}
is possibile to fetch for every book the related namePerson based on namePerson = personBook ?
Thank you very much!
You don't need a property personBook for your Books entity.
Let's create a Person managedObject (using Swift 1.0):
let person = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: context) as Person
person.idPerson = 23
person.namePerson = "John"
var error: NSError?
if !context.save(&error) {
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
When you create a Books managedObject, you can link it to person like this:
let book = NSEntityDescription.insertNewObjectForEntityForName("Books", inManagedObjectContext: context) as Books
book.bookTitle = "My book Title"
book.idBook = 2547
book.personRel = person
var error: NSError?
if !context.save(&error) {
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
Now, when you want to make a fetch on Books, you can do like this:
let fetchRequest = NSFetchRequest(entityName: "Books")
var error: NSError?
var booksArray = context.executeFetchRequest(fetchRequest, error:&error)
if let error = error {
println("Unresolved error \(error), \(error.userInfo)")
abort()
}
for book in booksArray as [Books] {
let person = book.personRel as Person
println(person.namePerson)
println(person.idPerson)
}
If I have understood your question / data structure correctly, you will want to do something like:
let fetchRequest = NSFetchRequest(entityName: "Books")
let bookPersonPredicate = NSPredicate(format: "personRel.idPerson == %#", person.idPerson)
fetchRequest.predicate = bookPersonPredicate

Store Integers in Core Data using Swift and XCode

While Strings appears to be fine I'm having some trouble storing Integers into Core Data. Following tutorials and reading available information out there doesn't seem to be helping me who has no Objective-C background. (Swift seemed like a straight forward language like the languages I'm fluent with PHP/OOPHP/JavaScript/VBScript/... thus I started playing with it and so far have been able to do everything I wanted, almost)
What I want to do now is, to receive the JSON data and store it into Core Data
Here's my Core Data
Entity name: Category
Its Attributes:
id Int16
title String
description String
My Swift model? file: Category.swift
import CoreData
class Category: NSManagedObject {
#NSManaged var id: Int //should I declare this as Int16?
#NSManaged var title: String
#NSManaged var description: String
}
I'm using SwiftyJASON extension? and NSURLSession protocol? to get the data and to parse it as follow:
import UIKit
import CoreData
class ViewController: UIViewController {
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
func fetchData() {
var url = NSURL.URLWithString("http://domain.com/index.php?r=appsync/read&id=category")
var session = NSURLSession.sharedSession()
session.dataTaskWithURL(url, completionHandler: {(data, response, error) in
// parse data into json
let json = JSONValue(data)
let entityDescription = NSEntityDescription.entityForName("Category", inManagedObjectContext: self.managedObjectContext)
let category = Category(entity: entityDescription, insertIntoManagedObjectContext: self.managedObjectContext)
for item in json.array! {
category.id = item["id"].string!.toInt()! //goes KABOOM!
category.title = item["title"].string!
category.description = item["description"].string!
managedObjectContext?.save(nil)
}
dispatch_async(dispatch_get_main_queue()) {
// do something
}
}).resume()
}
}
Let's assume the JASON data is:
[{"id":"1","title":"cat1","description":"blabala one"},{"id":"2","title":"cat2","description":"blabala two"}]
At line where it says category.id = item["id"].string!.toInt()! xCode goes KABOOM, what am I doing wrong here?
Notes/More questions:
I tried changing id type within Core Data to Int32 and then declaring it as just Int
in the model (and not Int16 or Int32) which reduced some errors but
xCode still crashes
Probably the way I'm looping stuff is not the best way to do this,
what's the better way of storing array of data into core data at
once?
Most of the tutorials I've seen there's no id's for Entities(tables), am I missing something here?
References:
SiftyJSON: https://github.com/lingoer/SwiftyJSON
Core Data tutorial:
http://rshankar.com/coredata-tutoiral-in-swift-using-nsfetchedresultcontroller/
EDIT > Working code:
Category.swift model file which can be auto generated using File>New>File>iOS>Core Data>NSManagedObject subclass [swift, no need for bridging header but you need to manually add #objc line as below]
import CoreData
#objc(Category) //Wouldn't work without this
class Category: NSManagedObject {
#NSManaged var id: NSNumber //has to be NSNumber
#NSManaged var title: String
#NSManaged var mydescription: String //"description" is reserved so is "class"
}
ViewController.swift
import UIKit
import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
func fetchData() {
var url = NSURL.URLWithString("http://domain.com/index.php?r=appsync/read&id=category")
var session = NSURLSession.sharedSession()
session.dataTaskWithURL(url, completionHandler: {(data, response, error) in
let json = JSONValue(data)
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext //this line had to be moved here
let entityDescription = NSEntityDescription.entityForName("Category", inManagedObjectContext: managedObjectContext)
for item in json.array! {
let category = Category(entity: entityDescription, insertIntoManagedObjectContext: managedObjectContext) //this line has to be in inside for loop
category.id = item["id"].string!.toInt()!
category.title = item["title"].string!
category.mydescription = item["description"].string!
managedObjectContext?.save(nil)
}
dispatch_async(dispatch_get_main_queue()) {
// do something
}
}).resume()
}
}
Sample fetching data code:
func requestData() {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Category")
request.returnsObjectsAsFaults = false
var results:NSArray = context.executeFetchRequest(request, error: nil)
//println(results)
for category in results {
var cat = category as Category
println("\(cat.id),\(cat.title),\(cat.mydescription)")
}
}
P.S. Make sure to Clean your project and delete the app from simulator after changing Model
Scalar types (integers, floats, booleans) in core data are broken in the current Swift beta (5). Use NSNumber for the properties, and file a radar.
object.intProperty = NSNumber(int:Int(item["id"] as String))
(Typed on the phone, so sorry if that's wrong, and I know it's disgusting code - hence, file a radar!)
Or, in your specific case, looking at the JSON, use String. Those IDs are coming in as strings anyway.
Updated for Swift 2
If your JSON data is really of type [[String:String]], you can use the following code in order to set category.id:
if let unwrappedString = item["id"], unwrappedInt = Int(unwrappedString) {
category.id = unwrappedInt
}

Resources