When I include a SQLite file with Objective-C under "Target - Build Phases - Copy Bundle Resources", this file will be completely copied to the target, i.e. device or simulator. On the target, I get the whole file: tables and contents (Records/rows).
Doing the same with Swift, the tables will be copied, but they are empty: no records/rows. :-(
Can I do something additional? Is there any "trick"?
How can I preload my core data with base-records using Swift???
I'm using Xcode v6.1.1, with Beta 6.2 it is the same.
This is my solution (for sqlite-files). I don't know why it is so "extravagant" in Swift. Perhaps there's an easier way?
Many THX to pbasdf!!!
Important: beneath the *.sqlite-file you must add the *.sqlite-shm and the *.sqlite-wal to your project
This files will automatically be added to "Target - Build Phases - Copy Bundle Resources"
This is based on the Standard-Template for the "AppDelegate.swift"…
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let dataName = „MyData.sqlite" // must be replaced by the name of your file!
let modelName = „MyData“ // must be replaced by the name of your model!
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent(dataName)
let fileManager = NSFileManager.defaultManager()
let bundle = NSBundle.mainBundle()
let fromURL = bundle.URLForResource(modelName, withExtension: "sqlite")
// check sqlite-file: must it be installed?
if !fileManager.fileExistsAtPath(url.path!) {
self.copySqlliteFiles(modelName, databaseName: dataName)
}
var error: NSError? = nil
var failureReason = "There was an error creating or loading the application's saved data."
if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
...}
return coordinator
}()
// MARK: - copy sqllite-files (c) by Ray Wenderlich & Team in „“Core Data by Tutorials“
// check sqlite-files: they must be installed...
func copySqlliteFiles(modelName: String, databaseName: String)
{
let bundle = NSBundle.mainBundle()
let baseDatabaseURL = bundle.URLForResource(modelName, withExtension: "sqlite")
let documentsURL = self.applicationDocumentsDirectory
let storeURL = documentsURL.URLByAppendingPathComponent(databaseName)
NSFileManager.defaultManager().copyItemAtURL(baseDatabaseURL!, toURL: storeURL,error: nil)
let baseSHMURL = bundle.URLForResource(modelName,withExtension: "sqlite-shm")
let shmURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent(databaseName + "-shm")
NSFileManager.defaultManager().copyItemAtURL(baseSHMURL!, toURL: shmURL, error: nil)
let walURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent(databaseName + "-wal")
let baseWALURL = bundle.URLForResource(modelName, withExtension: "sqlite-wal")
NSFileManager.defaultManager().copyItemAtURL(baseWALURL!, toURL: walURL, error: nil)
}
I think this probably has to do with how files are laid out on iOS. Specifically:
when you copy the source file (by Xcode or at install time) it gets placed into the application bundle/container directory (which is read-only)
The standard CoreData initialization code looks for the data store in the Documents directory.
Not finding your source data (because it's in the wrong place), CoreData creates a new empty store.
To fix this, you need to copy the store from the Application directory to the Documents directory if it doesn't already exist.
This link gives an explanation and example code to do that. But in Objective-C it looks like:
NSString *storePath = [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: #"MyDB.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
// Copy the default db if it doesn't already exist
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle]
pathForResource:#"MyDB" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath
toPath:storePath error:NULL];
}
}
I had a related issue related to this. I started working on some tutorials which used this line:
documentsURL.URLByAppendingPathComponent("StoreName")
At some point this stopped working. Data was not being written to the store anymore. To see why data was not being written, I tried opening the sqlite file but could not find it on my mac at the URL being used. The app folder was there, but there was no sqlite file. I looked back at older core data projects and found they used .sqlite extension. So I modified the line to:
documentsURL.URLByAppendingPathComponent("StoreName.sqlite")
This seemed to work so give it a try.
Related
I am seeking help to understand why a tutorial I am following is not working for me. I am running macOS 12.3.1, Xcode 13.3.1. The project is in Objective-C and using XIB.
This is a view-based NSTableView, using a folder of PNGs stored on my SSD for the imageView and the stringByDeletingPathExtension as stringValue for the cell's text field. I filled my code with NSLog calls to try and catch what could have been going awry.
Most setup is happening in applicationDidFinishLaunching:, where I initialise an NSMutableArray for the table's content, an NSString for the file path, then set up the file manager and the directory enumerator with said path (note: all working up to here).
Now comes the loop to populate the table contents' mutable array. I cannot understand why said loop gets skipped entirely! Its condition is to set an NSString equal to the nextObject of the directory enumerator. I am sure the loop gets skipped because the NSLog call after the loop runs!
Here is the entire code of applicationDidFinishLaunching:, including my comments and logs (I have just replaced my account name with ):
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_tableContents = [[NSMutableArray alloc] init];
NSString *path = #"/Users/<myUsername>/Developer/Apple-Programming-YT/Cocoa Programming/Flags/PNG/40x30";
// MARK: Debug 1
NSLog(#"path found: %#", path); // the correct path gets printed, as expected
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *directoryEnum = [fileManager enumeratorAtPath:path];
NSString *file;
// MARK: Debug 2
NSLog(#"Checking that file is empty: %#", file); // (null) gets printed, as expected
// MARK: Debug 3
if (file != directoryEnum.nextObject) {
NSLog(#"File cannot be assigned to the Directory Enumerator");
} else if (file == directoryEnum.nextObject) {
NSLog(#"File properly assigned. Proceed!"); // this gets printed! Is it correct?
} else {
NSLog(#"Something went wrong during assignment of nextObject to file");
}
while (file = [directoryEnum nextObject]) {
NSLog(#"While loop entered!"); // this doesn't get printed! Why?!
// MARK: Debug 4
NSLog(#"File: %#", file);
NSString *filePath = [path stringByAppendingFormat:#"/%#", file];
// MARK: Debug 5
NSLog(#"Image filepath: %#", filePath);
NSDictionary *obj = #{#"image": [[NSImage alloc] initByReferencingFile:filePath],
#"name": [file stringByDeletingPathExtension]};
[self.tableContents addObject:obj];
}
[self.tableView reloadData];
NSLog(#"Table View Reloaded"); // This gets printed!
}
I have uploaded the full app to GitHub, in case you may want to look at it and see if something else could be wrong, but every outlet, delegate, data source is connected.
Now for my diagnosis & ideas:
The Debug 3 mark is what I find most interesting. AFAIK file should still be (null), so how checking if it is equal to directoryEnum.nextObject returns YES?
I created Debug 3 because the NSLog checking whether the loop had been entered didn't get printed. I therefore assumed the condition for the while loop had a problem.
I then tried to create a do-while loop instead of this while loop and, of course, the code ran. For the log with "Image filepath" it returned the address above followed by (null), as if it didn't find the file. But how is it possible if the file is indeed there? Do I require some sort of permission to access it? Being the object empty, the next line in the console was quite clear: "attempt to insert nil object from objects[1]".
But now, how do I solve this?
Any help here is much appreciated. If you download it from GitHub, please replaces the *path string with a folder of PNGs on your SSD.
Thank you.
I don't think you can access the filesystem directly with a path like that any more. If you check the value of file in your code, it is nil, which means that file == directoryEnum.nextObject will evaluate to true.
You have to create a path starting with NSHomeDirectory() or similar and add components to it. This makes a path that goes via your application support folder, which contains an alias to the desktop. I'm not sure why that's OK and accessing it directly is not, but I'm not a Mac developer.
I'd have to say following a tutorial as old as that, you're going to struggle with a lot of things.
I'm facing a problem I using the FSEvent to catch the file created in my app but I also want to check the file extension (ex: photo/ video type) are allowed, otherwise these files will not be copied. How can I check the file extension before copying in Cocoa? Any suggestion would be appreciated. Thanks
Please note I don't use NSFileManager to copy file.
Use following code to check for movies or audio file types. Full list of UTI-Types in the documentation.
NSString * fileUTI = nil;
BOOL success = [self getResourceValue:& fileUTI forKey:NSURLTypeIdentifierKey];
if (success && [uti isKindOfClass:[NSString class]]) {
BOOL fileConformsToUTI = UTTypeConformsTo(fileUTI, kUTTypeMovie);
fileConformsToUTI = fileConformsToUTI || UTTypeConformsTo(fileUTI, kUTTypeAudio);
// check and do the copy
}
fileExistsAtPath is returning NO for a directory that exists. If I have the following code:
NSURL *newDirectoryPath = ... // path does not begin with a tilda
// and is created using URLByAppendingPathComponent:isDirectory:
BOOL directoryExists = [self.fileManager fileExistsAtPath:[newDirectoryPath absoluteString]];
if (NO == directoryExists)
{
BOOL ret = [self.fileManager moveItemAtURL:self.currentPresentationDirectoryPath toURL:newDirectoryPath error: &err];
// ret is YES, err is nil
directoryExists = [self.fileManager fileExistsAtPath:[newDirectoryPath absoluteString]];
}
Even though the directory has just been created successfully with moveItemAtURL, fileExistsAtPath is still returning NO.
I know the documentation says this:
Attempting to predicate behavior based on the current state of the
file system or a particular file on the file system is not
recommended. Doing so can cause odd behavior in the case of file
system race conditions.
But I want to understand what the issue is here - if I close the app and relaunch it then the first check for fileExistsAtPath in the code above is still returning NO, even though the directory was previously successfully created during the prior execution of the code, and I can see the directory in the Organizer, and I can also successfully read from the contents of the directory etc. etc.
P.S. is there no fileExistsAtURL: method?
If you have an NSURL, -absoluteURL won't return a usable path for NSFileManager. It will return the absolute URL with the file:// prefix. E.g.: file:///path/to/file.
Instead try to use an other method, like -path. Check if that works.
NSURL *myURL = /* some url */;
NSString *myPath;
BOOL exi;
myPath = [myURL path];
exi = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if(!exi) {
NSLog(#"File does not exist");
}
Okay,
So I have a Cydia app that I need to update. I am aware with Cydia apps that they don't have a Documents folder, so you have to make one. And here's how I made it before in iOS 4 (which doesn't work on iOS 5):
mkdir("/var/mobile/Library/APPNAME", 0755);
mkdir("/var/mobile/Library/APPNAME/Documents", 0755);
NSString *foofile = #"/var/mobile/Library/APPNAME/Documents/database.db";
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:foofile];
if (fileExists == TRUE) {
NSLog(#"already exists");
} else {
NSLog(#"doesn't exists");
NSFileManager *fileManager = [[NSFileManager defaultManager]autorelease];
NSError *error;
NSString *documentDBFolderPath = #"/var/mobile/Library/APPNAME/Documents/database.db";
NSString *resourceDBFolderPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"database.db"];
[fileManager copyItemAtPath:resourceDBFolderPath toPath:documentDBFolderPath error:&error];
}
I also included code that copies the database file to that folder, too. That doesn't work (even when I create the folder manually via SSH).
Please help! Thanks.
Here is the method I made to create directories
-(void)createDirectory:(NSString *)directoryName atFilePath:(NSString *)filePath
{
NSString *filePathAndDirectory = [filePath stringByAppendingPathComponent:directoryName];
NSError *error;
if (![[NSFileManager defaultManager] createDirectoryAtPath:filePathAndDirectory
withIntermediateDirectories:NO
attributes:nil
error:&error])
{
NSLog(#"Create directory error: %#", error);
}
}
Try using createDirectoryAtURL:withIntermediateDirectories:attributes:error:.
NSFileManager Class Reference:
createDirectoryAtURL:withIntermediateDirectories:attributes:error:
Creates a directory with given attributes at the specified path.
Parameters
url - A file URL that specifies the directory to create.
If you want to specify a relative path, you must set the
current working directory before creating the corresponding
NSURL object. This parameter must not be nil.
createIntermediates - If YES, this method creates any non-existent
parent directories as part of creating the directory in url. If NO,
this method fails if any of the intermediate parent directories does
not exist. This method also fails if any of the intermediate path
elements corresponds to a file and not a directory.
attributes - The file attributes for the new directory and any newly created
intermediate directories. You can set the owner and group numbers,
file permissions, and modification date. If you specify nil for this
parameter or omit a particular value, one or more default values are
used as described in the discussion. For a list of keys you can
include in this dictionary, see “Constants” (page 54) section lists
the global constants used as keys in the attributes dictionary. Some
of the keys, such as NSFileHFSCreatorCode and NSFileHFSTypeCode, do
not apply to directories.
error - On input, a pointer to an error object. If an error occurs,
this pointer is set to an actual error object containing the error
information. You may specify nil for this parameter if you do not
want the error information.
Return Value
YES if the
directory was created or already exists or NO if an error occurred.
Check NSFileManager's class reference. To create folders you need createDirectoryAtPath:withIntermediateDirectories:attributes:error:
Superb Techotopia explanation of iOS5 filesystem
In Swift, returns true if exists or created.
func ensureDirectoryExists(path:String) -> Bool {
if !NSFileManager.defaultManager().fileExistsAtPath(path) {
do {
try NSFileManager.defaultManager().createDirectoryAtPath(path, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error)
return false
}
}
return true
}
i created a sqlite file with "Lita", i copied it (dragging it) into xcode in the "Other Sources" folder (and i checked the square to copy the file). I then tried to check wether the file is copied or not, but in this folder : there is nothing copied into Documents? :
my path : /Users/MyProfile/Library/Application Support/iPhone Simulator/4.3/Applications/154BC5C5-FF55-48B5-A7A0-A80496212ACB/Documents
and my code that should copy it, or maybe there's something i misunderstood?
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSLog(#"my path : %#",self.applicationDocumentsDirectory);
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"myCD.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"myCD" ofType:#"sqlite"];
if (defaultStorePath) {
NSLog(#"in if defaultStorePath");
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
So the sqlite file should be copied into the iphone simulator/4.3/etc. but nothing is in there...
Thanks
You are properly copying the file.
However, if it is not a Core Data generated store file, Core Data cannot use it as a store.
Core Data is not a simple SQLite wrapper but instead using a SQLite store is but one of the Core Data store options. Core Data has to structure the store in a specific way and the Core Data sqlite schema is undocumented. You can't just drop any SQLite file in and use it as a Core Data store.
You will need to import the data into Core Data and have Core Data create the store and write data to it. Then you can include that file in the app bundle.