swift fetch a specific uiimage from core data - ios

I'm pretty new to swift and core data. So if this logic doesn't make sense, please let me know.
I'm trying to save user's images to core data and be able to delete selected one.
I have Entity "Media", attribute "photo" type "binary data"
Here I convert UIImage to NSData
let data = UIImagePNGRepresentation(addedImage!) as NSData?
And save it.
let newPhoto = NSEntityDescription.insertNewObject(forEntityName: "Media", into: context)
newPhoto.setValue(imageData, forKey: "photo")
Here I try to retrieve that one selected image from core data using predicate
func deleteImage(imageData: NSData) {
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Media")
request.returnsObjectsAsFaults = false
request.predicate = NSPredicate(format: "photo =%#", imageData)
do {
let results = try context.fetch(request)
...
And I'm getting this error message.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[OS_dispatch_data _bytesPtrForStore]: unrecognized selector sent to instance
Saving and getting all images from entity "Media" is working perfectly. I just can't get the specific one.
Any thoughts/suggestions?
Thanks.

Saving images like this, with external storage enabled for the property, can be OK. But there are some very unusual things going on here.
The common approach would be to have the image be part of an entity with other information, but not as the primary value of the entity. For example an entity type that represents a person and has name, address, etc, and a picture. Storing just a picture in an entity is kind of weird. You're not getting much of anything from using Core Data over something simpler. If nothing else I'd guess you had a photo ID, or title, or some other data in the entity.
The second thing relates to that-- it's extremely unusual to try to fetch using a predicate based on a binary data property. I guess it might work but only if you can ensure that the predicate value is identical to one in the persistent store all the way through-- not a single bit changed in the binary blob. That might be true but I wouldn't rely on UIKit not doing something along the way that makes it subtly different. I've never seen that particular error message but I'm not surprised that this isn't working.
Finally, it's also unusual (though not quite so strange as the above) to delete an object based on a fetch request. You would have fetched these objects before to get the images. Why fetch the same object again just to delete it? Delete the one you already fetched.
A more reliable approach would be to at least include a unique ID in the entity, which might be an integer or a UUID or whatever else works for you. Fetch this entity and display images using the photo property. When you want to delete one, take the managed object from your fetch and tell the context to delete that one. Or if fetching is necessary for some reason, use the unique ID in the predicate instead of the binary data.

You better stop doing it. Saving heavy data (image, audio, video, ...) to core data will greatly decrease fetching speed when you have more records. Instead, save image to application folder, and then only save its url to core data.

Related

Is there a way to access properties of an x-coredata:// object returned from an NSFetchRequest?

TL;DR: Is there a way to programmatically read/recall (NOT write!) an instance of a Core Data entity using the p-numbered "serial number" that's tacked on to the instance's x-coredata:// identifier? Is this a good/bad idea?
I'm using a method similar to the following to retrieve the instances of an Entity called from a Core Data data store:
var managedContext: NSManagedObjectContext!
let fetchRequest : NSFetchRequest<TrackInfo> = TrackInfo.fetchRequest()
fetchResults = try! managedContext.fetch(fetchRequest)
for (i, _) in Global.Vars.numberOfTrackButtons! {
let workingTrackInfo = fetchResults.randomElement()!
print("current track is: \(workingTrackInfo)")
The list of tracks comes back in fetchResults as an array, and I can select one of them at random (fetchResults.randomElement()). From there, I can examine the details of that one item by coercing it to a string and displaying it in the console (the print statement). I don't list the code below, but using workingTrackInfo I am able to see that instance, read its properties into other variables, etc.
In the console, iOS/Xcode lists the selected item as follows:
current track is: <MyProjectName.TrackInfo: 0x60000374c2d0> (entity:
TrackInfo; id: 0xa7dc809ab862d89d
<x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22>;
data: <fault>)
The line beginning with x-coredata: got my attention. It's formatted like a URL, consisting of what I assume is a UUID for the specific Core Data store associated with the current build of the app (i.e. not a stable address that you could hardcode; you'd need to programmatically look up the Core Data store, similar to the functions we use for programmatically locating the Documents Folder, App Bundle, etc.) The third item is the name of the Entity in my Core Data model -- easy enough.
But that last number is what I'm curious about. From examining the SQLite database associated with this data store, it appears to be a sort of "instance serial number" associated with the Z_PK field in the data model.
I AM NOT interested in trying to circumvent Core Data's normal mechanisms to modify the contents of a managed object. Apple is very clear about that being a bad idea.
What I AM interested in is whether it's possible to address a particular Core Data instance using this "serial number".**
In my application, where I'm randomly selecting one track out of what might be hundreds or even thousands of tracks, I'd be interested in, among other things, the ability to select a single track on the basis of that p-number serial, where I simply ask for an individual instance by generating a random p-number, tack it on to a x-coredata:// statement formatted like the one listed above, and loading the result (on a read-only basis!) into a variable for further use elsewhere in the app.
For testing purposes, I've tried simply hardcoding x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22 as a URL, but XCode doesn't seem to like it. Is there some other data Type (e.g. an NSManagedObject?) that allows you to set an x-coredata:// "URL" as its contents?
QUESTIONS: Has anyone done anything like this; are there any memory/threading considerations why grabbing instance names in this manner is a bad idea (I'm an iOS/Core Data noob, so I don't know what I don't know; please humor me!); what would the syntax/method for these types of statements be?
Thanks!
You are quite close.
x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22
is the uriRepresentation() of the NSManagedObjectID of the record.
You get this URL from an NSManagedObject with
let workingTrackInfo = fetchResults.randomElement()!
let objectIDURL = workingTrackInfo.objectID.uriRepresentation()
With this URL you can get the managed Object ID from the NSPersistentStoreCoordinator and the coordinator from the managed object context.
Then call object(with: on the context to get the object.
let persistentStoreCoordinator = managedContext.persistentStoreCoordinator!
if let objectID = persistentStoreCoordinator.managedObjectID(forURIRepresentation: objectIDURL) {
let object = managedContext.object(with: objectID) as! TrackInfo
print(object)
}

Creating a PDF (Or Other viewable format, such as .docx) from CoreData using swift

Description
Hello! So i am saving several strings of user inputed data into CoreData using a handful of NSManagedObject subclasses. Since the application is actually a machine inspection, it is necessary to print/email the customer entered information out in a somewhat structured format. Below you will find pictures/examples of how i am saving the data. Please be kind to me, for it is my birthday <3
Code examples
Below is an example of how i am saving the information. In the example, the CoreData entity is "CrawlerThree" and the data is crawlerDistance.
#IBAction func save(_ sender: AnyObject) {
let appDel:AppDelegate = (UIApplication.shared().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let entity1 = NSEntityDescription.insertNewObject(forEntityName: "CrawlerThree", into:context) as NSManagedObject as! CrawlerThree
entity1.crawlerDistance = distance.text
}
The rest of the saves are almost identical to this, with varying entities and data, all of which are strings. I know this is a rather vague post, but i really do need to know what i need to use to format this information. It is worth noting that i already know how to actually retrieve the string's from CoreData, I am simply ignorant on how to actually process them to some other form.
The Simplest solution will be the best solution for my problem! Thanks in advance!
I would divide the problem into two.
Output from Core Data to screen
Export as PDF
It sounds as if you know how to get your strings from Core Data already, so you just have to display them in a suitable way.
Fortunately whatever you use on screen: UIView, UITableView etc. can be saved as a PDF using e.g. the (Swift) answer to How to Convert UIView to PDF within iOS?

iOS Core Data Fetch Request, how to use

I am using Core Data to see whether messages in a table view have been seen before by the user. The way I do this is to save the message Id to Core Data the first time it is seen, and then I run a fetch request when I update the table view to see if there is an entry in the persistent memory with the same Id.
Now what I want to know is how I should most effectively implement my fetch request, based on how time consuming it is. Should I either run a request that returns all saved message Ids as an array when the view is loaded, and then in cellForRowAtIndexPathcheck if that array contains that cell's message Id, or run the fetch request with a predicate in cellForRowAtIndexPath? The latter would be my preferred method, but If i have 100 or so cells I wondered if this would be poor etiquette.
Any help would be very much appreciated.
This is my fetch Request :
func persistQuery(predicateValueString: String!) -> Bool! {
let fetchRequest = NSFetchRequest(entityName: "LogItem")
let predicate = NSPredicate(format: "itemText == %#", predicateValueString)
fetchRequest.predicate = predicate
var didFindResult = true
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
if fetchResults.count == 0 {
didFindResult=false
}
}
return didFindResult
}
The best way is to use a NSFetchedResultsController. It will optimize the fetching and the memory footprint as well. It is specifically designed for table views.
To get started, take a look at the Xcode template (Master/Detail, check Core Data). It is really quite simple.
Make sure you also implement the delegate methods - they will automatically be called when your managed objects change, so there is only minimal code that is executed to update the UI (an only if the object is actually on screen).
Presumably each of your table view cells represent a LogItem (the NSManagedObject subclass) with a property to indicate the read status. Once you change that, the delegate method will try to update it based on the index path.
That's all there is to it. With the fetched results controller you get a lot of optimization for free, so I would strongly recommend using it whenever you populate a table view with Core Data entities.

Asynchronous Fetching Swift Xcode

I use a collection view to display images stored in CoreData, only a few (around 15) but when I scroll down it lags, It seams to be because I am not fetching my data asyncrhronously, is it possible with only 15 UIImages?
So here is my problem I cannot find a descent tutorial on asynchronous fetching in swift anywhere ,3 days I am looking. Sorry if I didn't search well.
This is what arrived to do with some bits from tutorials
let entity = NSEntityDescription.entityForName("Dreams", inManagedObjectContext: contxt)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entity
let asyncFetch = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) {
(result:NSAsynchronousFetchResult!) -> Void in
if result.finalResult!.count > 0 {
self.dreams = result.finalResult! as [Dream]
}
NSLog("Done.")
}
Dreams is an array of type [Dream] but when I display the collection view nothing appears
This is how I did it previously
let fetchRequest = NSFetchRequest(entityName: "Dreams")
if let fetchResults = contxt.executeFetchRequest(fetchRequest, error: nil) as? [Dream] {
dreams = fetchResults
}
It worked very well,
Thank you very much for your help
Unfortunately, I don't have time to offer specific code in this instance. I did want to present a basic strategy, however. Fetching on the main thread is exceedingly unlikely to be your problem here, but fetching everything at once definitely could be, particularly with large binary data involved. This should be sped up significantly if:
You start using an NSFetchedResultsController with a good batch size rather than a raw fetch request. NSFetchedResultsController is a little finicky about updates with collection views, in ways which are beyond the specific scope of this question, but they work very well once set up correctly.
You store the images as files instead of in Core Data itself, and store the path to the file instead. Then that image data won't get loaded into memory every time you fault in the object, and you can simply load and display the images as the cells come on screen using that file URL.
People who insist on storing binary data in Core Data usually find that storing the data as the only attribute in a separate "Image" entity with a relationship to the entity you are displaying helps. The image object (along with its binary image data) can then be kept a fault, and thus not in memory, even when the object you fetch for the collection view is in memory. I, however, prefer not to store them in Core Data, and simply handle the resulting atomicity gotchas and the possibility the file Core Data points to got deleted somehow.

what's the correct way to store an NSURL in Core Data?

I'm trying to archive / unarchive NSManagedObjectIDs in Core Data objects so that next time my app starts up I can retrieve these IDs and use them to fetch specific objects.
I tried "archiving" the ID like this:
//defaultConfiguration is an NSManagedObject defined elsewhere and it works just fine...
// and newObject is also properly initialized, bound to a context,etc...
ArchivedID* newID = [NSEntityDescription insertNewObjectForEntityForName:#"ArchivedID" inManagedObjectContext:managedObjectContext];
[self.defaultConfiguration addArchiveIDObject:newID];
newID.idURI = [[newObject objectID] URIRepresentation];
[managedObjectContext save:&error];
And then unarchiving like this (I'm just going for [anyObject] cause i'm testing and there's only one at this point):
NSManagedObjectID* ID = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:[defaultConfiguration.archiveID anyObject]];
But when I try getting the URL back like above, I get the following exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ArchivedID relativeString]: unrecognized selector sent to instance 0x7f59f20'
The attribute in the entity was set up through Xcode to "transformable" and I left the transformer value field in Xcode empty since the Core Data documentation seems to imply if empty it's use the default transformer.
What am I doing wrong?
It's possible you can solve this problem without storing URLs. Consider adding a boolean flag to your model and marking the objects you wish to retrieve as true, then fetch flagged objects next time your app starts.
However, you could try getting the string version of the URL with -absoluteString and storing that.
Ok... It's been 14hrs straight of coding.. I'm an.. eh.. idiot:
I forgot to access the attribute in the ArchivedID object. That is:
NSManagedObjectID* ID = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:[defaultConfiguration.archiveID anyObject]];
should be
NSManagedObjectID* ID = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:[[defaultConfiguration.archiveID anyObject] idURI]];
Hmmm. Seems like there is a much simpler way to do this with CoreData. Remember, CoreData is an object graph. It's good at keeping track of relationships.
Why not just have an entity in CoreData that keeps a one-to-many relationship to the objects you like? Then, you can just insert/remove/search/iterate/whatever over the collection of objects.
I like what #kevboh suggested. You might also consider storing the objectIDs of specific NSManagedObjects in NSUserDefaults. E.g.:
[[NSUserDefaults standardUserDefaults] setObject:featuredNSManagedObjectIDArray
forKey:#"FeaturedNSManagedObjectIDs"];

Resources