I have CoreData app that stores some BLOBs in external files ("allows external storage" flag).
Files are images, PDFs, excel files etc. I wanna use QLPreviewController ti preview it. But it does not support CoreData from scratch. So, I should first copy file to tmp folder. I am sure there should be better way to do that.
Does there is any?
According to the documentation for QLPreviewItem the URL returned by previewItemURL must be a file URL.
Thus, you must be able to give it a URL for a file that lives on disk. Apple does not provide an official way to get the URL for data stored externally. Furthermore, "smaller" files would be stored as a BLOB in the SQL database anyway.
The two most viable options are to either copy the contents into a temporary file when needed, or store the file on disk yourself, and keep the URL (or better yet... a unique identifier) in the core data model.
I'd go with the second method.
If you store the files in a subdirectory of the directory containing your core data store, you can just keep a UUID in the database. You can then identify the file, even if you want to move it to a different location, and you don't have to go change all the entities in the store.
Let's say you have a directory named "externalFiles" in the same directory as your persistent store.
If you used a special entity, you could add two simple attributes (identifier and title) for the particular item. The identifier can be the string representation of NSUUID.
You will probably already want to get at the URL...
- (NSURL*)URL {
// Separated on multiple lines for readability
NSString *identifier = [self valueForKey:#"identifier"];
NSURL *url = self.objectID.persistentStore.URL;
url = [url URLByDeletingLastPathComponent];
url = [url URLByAppendingPathComponent:#"externalFiles"];
url = [url URLByAppendingPathComponent:identifier];
return url;
}
You can then make your NSManagedObject subclass conform to QLPreviewItem protocol by implementing the two methods previewItemURL and previewItemTitle.
- (NSURL*)previewItemURL {
return self.URL;
}
- (NSURL*)previewItemTitle {
return self.title;
}
And then, you can just pass your core data instances to the QLPreviewControllerDataSource because they can now be treated as QLPreviewItems.
Related
Within their documentation on storage, Apple uses URLS for file paths and, so far as I can tell, never gets explicit about what "meals" is within this context. And that's causing me no end of grief. Is "meals", in the following, a file or a folder?
Part way down this page, is the following on the subject of a storage location:
To create a file path to data
In Meal.swift, below the //MARK: Properties section, add this code:
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for:
.documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("meals")
You mark these constants with the static keyword, which means they
belong to the class instead of an instance of the class. Outside of
the Meal class, you’ll access the path using the syntax
Meal.ArchiveURL.path. There will only ever be one copy of these
properties, no matter how many instances of the Meal class you create.
The DocumentsDirectory constant uses the file manager’s urls(for:in:)
method to look up the URL for your app’s documents directory. This is
a directory where your app can save data for the user. This method
returns an array of URLs, and the first parameter returns an optional
containing the first URL in the array. However, as long as the
enumerations are correct, the returned array should always contain
exactly one match. Therefore, it’s safe to force unwrap the optional.
After determining the URL for the documents directory, you use this
URL to create the URL for your apps data. Here, you create the file
URL by appending meals to the end of the documents URL.
I'm using Core Data in an iOS 7+ app that does not need to save user's data, all data the app needs is requested to services and it can be recovered at any time. So, if I change my data model in a next app update, I have no problem in deleting all the previous data and requesting it all again. But I don't know how to simply replace the previous data model with the new one, without performing a migration since it looks that I don't need to do that...
Thanks in advance
Working Swift solution if you target iOS 9 or higher
The shared CoreData manager:
class CoreDataContext {
static let datamodelName = "CoreDataTests"
static let storeType = "sqlite"
static let persistentContainer = NSPersistentContainer(name: datamodelName)
private static let url: URL = {
let url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].appendingPathComponent("\(datamodelName).\(storeType)")
assert(FileManager.default.fileExists(atPath: url.path))
return url
}()
static func loadStores() {
persistentContainer.loadPersistentStores(completionHandler: { (nsPersistentStoreDescription, error) in
guard let error = error else {
return
}
fatalError(error.localizedDescription)
})
}
static func deleteAndRebuild() {
try! persistentContainer.persistentStoreCoordinator.destroyPersistentStore(at: url, ofType: storeType, options: nil)
loadStores()
}
}
call loadStores only once in the appDelegate and deleteAndRebuild when you want to delete and rebuild the database :)
Case 1: You're using a SQLite store
This applies if your store type is NSSQLiteStoreType. Even if you plan to delete your data from time to time, it's not a bad idea to stick to SQLite, as it gives you the flexibility to keep your cached data on disk as long as you wish, and only delete it when you change your model and you don't want to apply any migrations.
Quick solution? Delete your NSPersistentStoreCoordinator's store at startup, when you're initializing Core Data.
For instance, if you're using the default SQLite store, provided by Apple's boilerplate code:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"cd.sqlite"]
you can simply delete the file:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
then use storeURL to add a new persistent store as usual.
Your NSPersistentStoreCoordinator won't complain if you have a new model and you won't need any migrations, but your data will be lost, of course.
It goes without saying that you can decide to use this solution when you detect a change in the data model, leaving the store alone if there's no change, so that you can preserve your cached data as long as necessary.
UPDATE:
As suggested by TomHerrington in the comments, to be sure you've removed the old store completely, you should also delete journal files, which might come back and haunt you in the future, if you don't take care of them.
If your store file is named cd.sqlite, like in the example, the additional files to be removed are cd.sqlite-shm and cd.sqlite-wal.
WAL journaling mode for Core Data was introduced as the default in iOS 7 and OSX Mavericks, as reported by Apple in QA1809.
Case 2: Use an in-memory store
As suggested, you could switch to an in-memory store, using NSInMemoryStoreType instead of NSSQLiteStoreType. Erasing the store is much easier in this case: all your data resides in memory, all of it will disappear when your app stops running, nothing will remain on disk for you to clean. Next time, you could potentially load a completely different model, without any migrations, as there's no data to migrate.
However, this solution, implemented as it is, wouldn't let you cache data between sessions, which looks like something you'd like to do between app updates (i.e., the store must be erased only when the app is updated and the model changes, while it could be useful to keep it on disk otherwise).
Note:
Both approaches are viable, with their pros and cons, and I'm sure there could be other strategies as well. In the end, you should have all the elements to decide what the best approach would be in your specific case.
I think destroyPersistentStoreAtURL method of NSPersistentStoreCoordinator is what you want.
It will remove the data store and journal files and all other things that need to be removed.
Look at Apple documentation.
I am currently writing an iOS app in which my intent is to save data objects associated with the app in a DB created in Core Data. I have successfully created the DB, and plan to synchronize the data objects among different devices logged in to the same iCloud-account through iCloud. In the middle of all this, I would also like media files to be associated with the distinct data objects. However, I do not wish to save the binary data constituting a media file directly to the DB. Thus I need some kind of way to preserve the reference to the correct media file in the DB. My immediate thought was to place every media file in a specific folder, turn on iCloud sync for that folder, and save the media files' filenames to the DB. However, I am not able to retrieve the file path of any media files.
Below is a code snippet from the app. When that code is run, an assetCollection as well as a photosAsset with an index exists, and is displaying an image in the view controller. Despite this, none of the if-sentences prints any output (all of the references are nil).
if let a: String = self.photosAsset[self.index].bundleIdentifier {
println(a)
}
if let a = self.photosAsset[self.index].bundleURL {
println(a)
}
if let a: String = self.photosAsset[self.index].resourcePath {
println(a)
}
if let a = self.photosAsset[self.index].resourceURL {
println(a)
}
Any thoughts on how to retrieve the file path of an image file that is being displayed in this way? Any solutions on how to retrieve file paths in general that could be applicable to this problem?
Any thoughts on my approach in general would also be greatly appreciated.
I think that if you are storing the photos as xcassets, then you may not be able to access a path for them, as this type can sometimes be compressed (so it seems Apple don't allow it) e.g. see the answer posted for Access Asset Catalog pathForResource
I would create a custom directory structure to store the photos, rather than store them in the bundle, e.g. in /Documents/
This would allow you to use classes such as NSFileManager to find all photos of certain types etc and load them when need be.
For information about where Apple recommends you store things see https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
I have the following requirement: create and populate a SQLite database with data from a .xml file, this file can have a different structure any time so I cannot create the NSManagedObjectModel with Xcode, it must be at runtime. I've successfully created a NSManagedObjectModel programmatically, created the persistent store and populated the sqlite file with data from the .xml . However, the next time I use the app the persistent store is not compatible with the NSManagedObjectModel (I don't want to recreate the Model every time I run the app, just the first time). Is there any way to save the model I created programmatically and load it the next time it is needed? All I can see in the examples are models being loaded from the NSBundle.
Is there any way to save the model I created programmatically and load it the next time it is needed?
Yes. NSManagedObjectModel conforms to NSCoding, which means that you can easily convert it to/from NSData, and saving and reading NSData is easy.
To save a model:
NSString *modelPath = // path where you want to save
NSData *modelData = [NSKeyedArchiver archivedDataWithRootObject:self.managedObjectModel];
[modelData writeToFile:modelPath atomically:YES];
To read a saved model:
if ([[NSFileManager defaultManager] fileExistsAtPath:modelPath]) {
NSData *savedModelData = [NSData dataWithContentsOfFile:modelPath];
NSManagedObjectModel *savedModel = [NSKeyedUnarchiver unarchiveObjectWithData:savedModelData];
}
I'm not sure if you are saying that the data in the xml file is changing each time or what. It sounds like you are referring to the data, not the data model. I can't answer specifically, but I would take the approach as follows.
If the data in the xml file is structured the same or close to the same each time, I would create a data model to match that.
Then I would write some sort of parser class that would read the xml and parse it into the Core Data data store according to you "ManagedObjectModel" or data model.
I have seen the error you are talking about when you change the datastore outside of Core Data. You need to let Core Data handle all the reading and writing to the data store or else Core Data will tell you basically that "Your Persistent Store was created or altered by something other than your ManagedObjectModel". I think this is what is happening.
I know I am not using the terminology exactly as Core Data puts it, but Core Data is confusing and I'm trying to convey the message and understanding.
I would also look in to using MagicalRecord. It Drastically makes Core Data easier to work with and there is a great tutorial on www.raywenderlich.com which you can find Here
I really hope this helps you out some. If not, please post some sample code or maybe an example of that xml you are referring to.
Good Luck
Purpose: I have to create entities from files.
So entities represent my data model in CoreData and files have all information for this entities.
All files I get from Internet. For this I use AFNetworking framework.
How I get files (algorithm):
Request plist file. Plist file has values to other urls that I have to download.
When plist was downloaded to my Documents directory on device. I parse it.
When I parse plist I grab url from each item from NSDictionary that represent this plist.
Then I request zip files from this urls.
After zip files were downloaded I unzip them and go to the next step.
Parse unzipped files and create data model.
It is my problem. I have version of file that stored locally and that stored on the server and when version on the server changed I need to reload my data model with actual data. The bad way it is load all data from server again next delete all entities in storage and make new entities from new data. But it is not professional way at first and second it is an additional burden on the traffic, because if I have just one entity that I need to reload why I have to reload other entities that are in the actual state. So maybe someone knows best practice with this question. Of course I can create my solution and it will work, but I want to see how people solve this problem and figure out with the differences in my solution and in the other solutions also.
This is trivial. You simply keep an attribute with the time stamp of the last update and just request the changed and new entities from your server. Then you insert, update or delete as appropriate.
It sounds like you are talking about find-or-create. Depending on the size of the data set and your performance requirements you can do this a couple of ways:
The first way is to fetch your existing Core Data objects and store them in an a dictionary with a unique attribute of the entity serving as the key. Then when you download the new data you can take this key for each parsed object and check your dictionary to find (and optionally update) any existing object, or create a new one:
NSArray *fetchedObjects = //Fetch the existing objects;
NSMutableDictionary *existingObjects = [NSMutableDictionary dictionary];
for (MyClass *object in fetchedObjects)
[existingObjects setObject:object forKey:object.uniqueKey];
//Now iterate through the new data (using JSON as example here)
NSDictionary *downloadedItems = //Download data and serialise into JSON
for (NSDictionary *item in downloadedItems){
NSString *uniqueValue = [item valueForKey:#"uniqueKey"];
MyClass *object = [existingObjects objectForKey:uniqueValue];
if (object==nil){
object = //Insert new MyClass entity into MOC
object.uniqueKey = uniqueValue;
//Set any other values you only need to set once
}
//Set any values you may need to update every time
//This would be where to check a modified date attribute
}
The second way is more sophisticated, and involves less memory overhead. It's described in the Efficiently Importing Data section of the Core Data Programming Guide.
The guide gives a good start but doesn't offer a complete solution; I attempted my own in an answer here: Basic array comparison algorithm