我一直在创建一个列表应用程序并用核心数据支持它。
我想要一个包含 10 个机场项目的默认列表,这样用户就不必从头开始。
有没有办法做到这一点?
任何帮助表示赞赏。提前致谢。
我一直在创建一个列表应用程序并用核心数据支持它。
我想要一个包含 10 个机场项目的默认列表,这样用户就不必从头开始。
有没有办法做到这一点?
任何帮助表示赞赏。提前致谢。
这是最好的方法(并且不需要 SQL 知识):
使用与 List 应用程序相同的对象模型创建一个快速的 Core Data iPhone 应用程序(甚至是 Mac 应用程序)。编写几行代码,将您想要的默认托管对象保存到存储中。然后,在模拟器中运行该应用程序。现在,转到 ~/Library/Application Support/iPhone Simulator/User/Applications。在 GUID 中找到您的应用程序,然后将 sqlite 存储复制到您的列表应用程序的项目文件夹中。
然后,像在 CoreDataBooks 示例中那样加载该存储。
是的,实际上 CoreDataBooks 示例就是这样做的,您可以在此处下载代码:示例代码
您所做的是使用正常过程创建内部存储(数据库)来初始化您的存储,就像您使用任何其他存储一样,然后您只需运行您的代码并让它执行 CoreDataBooks 示例中描述的代码(下面的代码片段)。初始化存储后,您将需要创建一个NSManagedObjectContext
并使用创建的持久存储对其进行初始化,插入您需要的所有实体,并保存上下文。
成功保存上下文后,您可以停止应用程序,然后转到 finder 并转到文件夹:~/Library/Developer
输入搜索 .sqlite 并在 /Developer 下查找,按日期排序将为您提供应该匹配的最新 .sqlite 数据库代码执行的时间,然后您可以使用此存储并将其添加为项目的资源。然后可以由持久存储协调器读取此文件。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"CoreDataBooks.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:@"CoreDataBooks" ofType:@"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}
希望有帮助。
-奥斯卡
使用这种方法,您无需制作单独的应用程序或具备任何 SQL 知识。您只需要能够为您的初始数据创建一个 JSON 文件。
我使用解析为对象的 JSON 文件,然后将它们插入核心数据中。我在应用程序初始化时执行此操作。我还在我的核心数据中创建了一个实体,指示是否已插入此初始数据,在我插入初始数据后,我设置了此实体,以便下次运行脚本时,它会看到初始数据已被初始化。
要将 json 文件读入对象:
NSString *initialDataFile = [[NSBundle mainBundle] pathForResource:@"InitialData" ofType:@"json"];
NSError *readJsonError = nil;
NSArray *initialData = [NSJSONSerialization
JSONObjectWithData:[NSData dataWithContentsOfFile:initialDataFile]
options:kNilOptions
error:&readJsonError];
if(!initialData) {
NSLog(@"Could not read JSON file: %@", readJsonError);
abort();
}
然后你可以像这样为它制作实体对象:
[initialData enumerateObjectsUsingBlock:^(id objData, NSUInteger idx, BOOL *stop) {
MyEntityObject *obj = [NSEntityDescription
insertNewObjectForEntityForName:@"MyEntity"
inManagedObjectContext:dataController.managedObjectContext];
obj.name = [objData objectForKey:@"name"];
obj.description = [objData objectForKey:@"description"];
// then insert 'obj' into Core Data
}];
如果您想更详细地了解如何执行此操作,请查看本教程: http ://www.raywenderlich.com/12170/core-data-tutorial-how-to-preloadimport-existing-data-updated
对于 10 个项目,您可以applicationDidFinishLaunching:
在您的应用程序委托中执行此操作。
定义一个方法,例如insertPredefinedObjects
,创建和填充负责管理您的机场项目的实体实例,并保存您的上下文。您可以从文件中读取属性,也可以简单地将它们硬连接到您的代码中。然后,在里面调用这个方法applicationDidFinishLaunching:
。
请记住,在遵循 CoreDataBooks 示例代码时,它可能违反了 iOS 数据存储指南:
https://developer.apple.com/icloud/documentation/data-storage/
我有一个应用程序因将(只读)预填充数据库复制到文档目录而被拒绝 - 因为它然后被备份到 iCloud - 苹果只希望这种情况发生在用户生成的文件上。
上面的指南提供了一些解决方案,但它们主要归结为:
将数据库存储在缓存目录中,并优雅地处理操作系统清除缓存的情况 - 您将不得不重建数据库,这可能对我们大多数人来说排除了它。
在数据库文件上设置一个“不缓存属性”,这有点神秘,因为不同的操作系统版本需要以不同的方式完成。
我不认为这太棘手,但请注意,要使示例代码与 iCloud 一起工作,您还有一些额外的工作要做......
这对我有用。这是Andrea Toso对此答案的修改,并受到此博客的启发。答案的唯一问题是使用 FileManager 移动 sqlite 文件时可能会丢失数据。我使用 replacePersistentStore 而不是 FileManager.default.copyItem 保存了大约 500 行数据
第 1 步
在另一个应用程序中填充您的核心数据并使用以下代码获取文件的路径:
let paths = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
print(documentsDirectory)
Step2
将您的 3 个扩展名为 .sqlite 的文件拖到您的 xCode 项目中。(请务必选择添加到目标选项)。
Step3
在 AppDelegate.swift 中创建函数来检查应用程序的首次运行
func isFirstLaunch() -> Bool {
let hasBeenLaunchedBeforeFlag = "hasBeenLaunchedBeforeFlag"
let isFirstLaunch = !UserDefaults.standard.bool(forKey: hasBeenLaunchedBeforeFlag)
if (isFirstLaunch) {
UserDefaults.standard.set(true, forKey: hasBeenLaunchedBeforeFlag)
UserDefaults.standard.synchronize()
}
return isFirstLaunch
}
Step4
将此函数复制到 AppDelegate.swift 中以获取应该移动 sqlite 数据库的 url:
func getDocumentsDirectory()-> URL {
let paths = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
步骤 5
用这个替换persistentContainer 的声明:
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "ProjectName")
let storeUrl = self.getDocumentsDirectory().appendingPathComponent("FileName.sqlite")
if isFirstLaunch() {
let seededDataUrl = Bundle.main.url(forResource: "FileName", withExtension: "sqlite")
try! container.persistentStoreCoordinator.replacePersistentStore(at: storeUrl, destinationOptions: nil, withPersistentStoreFrom: seededDataUrl!, sourceOptions: nil, ofType: NSSQLiteStoreType)
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
此答案仅适用于以下人群
我为 Android 应用程序制作了一个预填充的 SQLite 数据库。然后,当我制作应用程序的 iOS 版本时,我认为最好使用 Core Data。所以我花了很长时间学习 Core Data,然后重写代码来预填充数据库。学习如何在这两个平台上完成每一步都需要大量的研究和反复试验。重叠比我希望的要少得多。
最后,我决定使用我的 Android 项目中相同的 SQLite 数据库。然后我使用 FMDB 包装器直接访问 iOS 中的数据库。好处:
虽然我不后悔学习 Core Data,但如果我重新学习,我可以通过坚持使用 SQLite 来节省大量时间。
如果您从 iOS 开始,然后计划迁移到 Android,我仍然会使用 SQLite 包装器(如 FMDB)或其他一些软件来预填充数据库。虽然您可以从技术上提取使用 Core Data 预填充的 SQLite 数据库,但模式(表和列名等)将被奇怪地命名。
顺便说一句,如果您不需要修改您的预填充数据库,那么在安装应用程序后不要将其复制到文档目录。只需直接从捆绑包中访问它。
// get url reference to databaseName.sqlite in the bundle
let databaseURL: NSURL = NSBundle.mainBundle().URLForResource("databaseName", withExtension: "sqlite")!
// convert the url to a path so that FMDB can use it
let database = FMDatabase(path: databaseURL.path)
这样一来,您就没有两个副本。
更新
我现在使用SQLite.swift而不是 FMDB,因为它可以更好地与 Swift 项目集成。
因此,我开发了一种从字典(可能来自 JSON)加载并填充数据库的通用方法。它只能与受信任的数据(来自安全通道)一起使用,它不能处理循环引用,并且模式迁移可能会出现问题......但对于像我这样的简单用例来说应该没问题
来了
- (void)populateDBWithDict:(NSDictionary*)dict
withContext:(NSManagedObjectContext*)context
{
for (NSString* entitieName in dict) {
for (NSDictionary* objDict in dict[entitieName]) {
NSManagedObject* obj = [NSEntityDescription insertNewObjectForEntityForName:entitieName inManagedObjectContext:context];
for (NSString* fieldName in objDict) {
NSString* attName, *relatedClass, *relatedClassKey;
if ([fieldName rangeOfString:@">"].location == NSNotFound) {
//Normal attribute
attName = fieldName; relatedClass=nil; relatedClassKey=nil;
} else {
NSArray* strComponents = [fieldName componentsSeparatedByString:@">"];
attName = (NSString*)strComponents[0];
relatedClass = (NSString*)strComponents[1];
relatedClassKey = (NSString*)strComponents[2];
}
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", attName ]);
NSMethodSignature* signature = [obj methodSignatureForSelector:selector];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:obj];
[invocation setSelector:selector];
//Lets set the argument
if (relatedClass) {
//It is a relationship
//Fetch the object
NSFetchRequest* query = [NSFetchRequest fetchRequestWithEntityName:relatedClass];
query.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:relatedClassKey ascending:YES]];
query.predicate = [NSPredicate predicateWithFormat:@"%K = %@", relatedClassKey, objDict[fieldName]];
NSError* error = nil;
NSArray* matches = [context executeFetchRequest:query error:&error];
if ([matches count] == 1) {
NSManagedObject* relatedObject = [matches lastObject];
[invocation setArgument:&relatedObject atIndex:2];
} else {
NSLog(@"Error! %@ = %@ (count: %d)", relatedClassKey,objDict[fieldName],[matches count]);
}
} else if ([objDict[fieldName] isKindOfClass:[NSString class]]) {
//It is NSString
NSString* argument = objDict[fieldName];
[invocation setArgument:&argument atIndex:2];
} else if ([objDict[fieldName] isKindOfClass:[NSNumber class]]) {
//It is NSNumber, get the type
NSNumber* argument = objDict[fieldName];
[invocation setArgument:&argument atIndex:2];
}
[invocation invoke];
}
NSError *error;
if (![context save:&error]) {
NSLog(@"%@",[error description]);
}
}
}
}
并从 json 加载...
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"initialDB" ofType:@"json"];
NSData *jsonData = [NSData dataWithContentsOfFile:filePath];
NSError* error;
NSDictionary *initialDBDict = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers error:&error];
[ self populateDBWithDict:initialDBDict withContext: [self managedObjectContext]];
JSON 示例
{
"EntitieA": [ {"Att1": 1 }, {"Att1": 2} ],
"EntitieB": [ {"Easy":"AS ABC", "Aref>EntitieA>Att1": 1} ]
}
和
{
"Country": [{"Code": 55, "Name": "Brasil","Acronym": "BR"}],
"Region": [{"Country>Country>code": 55, "Code": 11, "Name": "Sao Paulo"},
{"Country>Country>code": 55, "Code": 31, "Name": "Belo Horizonte"}]
}
如何检查是否存在任何对象,如果不存在,使用一些数据创建一个?
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Settings"];
_managedObjectSettings = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
if ([_managedObjectSettings count] == 0) {
// first time, create some defaults
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:@"Settings" inManagedObjectContext:managedObjectContext];
[newDevice setValue:[NSNumber numberWithBool: YES ] forKey:@"speed"];
[newDevice setValue:[NSNumber numberWithBool: YES ] forKey:@"sound"];
[newDevice setValue:[NSNumber numberWithBool: NO ] forKey:@"aspect"];
[newDevice setValue:[NSNumber numberWithBool: NO ] forKey: @"useH264"];
[newDevice setValue:[NSNumber numberWithBool: NO ] forKey: @"useThumbnail"];
NSError *error = nil;
// Save the object to persistent store
if (![managedObjectContext save:&error]) {
NSLog(@"Can't Save! %@ %@", error, [error localizedDescription]);
}
}
另一种存储默认值的方法是通过 NSUserDefaults 找到的。(惊喜!)而且很简单。
有人建议,把它放进去applicationDidFinishLaunching
在给定 10 个默认值的情况下,Airport0 到 9
环境
NSUserDefaults *nud = [NSUserDefaults standardUserDefaults];
[nud setString:@"MACADDRESSORWHY" forKey:@"Airport0"];
...
[nud setString:@"MACADDRESSORWHY" forKey:@"Airport9"];
[nud synchronize];
或者
[[NSUserDefaults standardUserDefaults] setString:@"MACADDRESSORWHY" forKey:@"Airport9"]];
...
[[NSUserDefaults standardUserDefaults] synchronize];
然后,获取默认值。
NSString *air0 = [[NSUserDefaults standardUserDefaults] stringForKey:@"Airport0"];
由于大多数答案都很老,我推荐以下教程。它解释了如何做到这一点。