Upgrading my application to include prepopulated data - swift

I've decided to improve upon my initial design and pre-populate certain parts of my Application. I am trying to get the sqlite to be read following this tutorial: http://www.appcoda.com/core-data-preload-sqlite-database/
However the line of code that goes like this:
if !NSFileManager.defaultManager().fileExistsAtPath(url.path!) {
let sourceSqliteURLs = [NSBundle.mainBundle().URLForResource("CoreDataDemo", withExtension: "sqlite")!, NSBundle.mainBundle().URLForResource("CoreDataDemo", withExtension: "sqlite-wal")!, NSBundle.mainBundle().URLForResource("CoreDataDemo", withExtension: "sqlite-shm")!]
let destSqliteURLs = [self.applicationDocumentsDirectory.URLByAppendingPathComponent("CoreDataDemo.sqlite"),
self.applicationDocumentsDirectory.URLByAppendingPathComponent("CoreDataDemo.sqlite-wal"),
self.applicationDocumentsDirectory.URLByAppendingPathComponent("CoreDataDemo.sqlite-shm")]
var error:NSError? = nil
for var index = 0; index < sourceSqliteURLs.count; index++ {
NSFileManager.defaultManager().copyItemAtURL(sourceSqliteURLs[index], toURL: destSqliteURLs[index], error: &error)
}
}
isn't quite right for my application as I am using a shared application folder. The problem I am facing is that the application loads fine and passes the lines of code but when I am looking at the page that shows the info nothing is there. I downloaded the sqlite reader to see if there are objects in the database and there are so I know that isn't the issue. What modifications do I have to make to these lines of code in order for the share app group to perform this function correctly? The directory to the Shared App Group is something like this:
let directory = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.groupname.appname")
I am struggling to find any info on this any help would be greatly appreciated.

Maybe I'm missing something in your questions, but if all you want to do is change the destination directory, then just change these lines...
let destSqliteURLs = [
self.applicationDocumentsDirectory.URLByAppendingPathComponent("CoreDataDemo.sqlite"),
self.applicationDocumentsDirectory.URLByAppendingPathComponent("CoreDataDemo.sqlite-wal"),
self.applicationDocumentsDirectory.URLByAppendingPathComponent("CoreDataDemo.sqlite-shm")]
into this...
let baseURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.groupname.appname")
let destSqliteURLs = [
baseURL.URLByAppendingPathComponent("CoreDataDemo.sqlite"),
baseURL.URLByAppendingPathComponent("CoreDataDemo.sqlite-wal"),
baseURL.URLByAppendingPathComponent("CoreDataDemo.sqlite-shm")]

Related

Migration to change the configuration of CoreData

I started a macOS project using Default configuration of CoreData. Application was released and some users started to use it. Now, I need some data to be synced with iCloud and some data to be only stored locally. If I understand correctly, the only way I can achieve this is to create two different configurations (in CoreData data model), add the needed entities in each configuration, and configure the NSPersistentContainer accordingly.
However the above method might lead to some data loss since I wont be using the Default configuration anymore.
Is there any way I can "migrate" the data saved under the Default configuration to another configuration?
After some trial and error I found a solution that seems to do the work (however, it seems dirty).
First, when instantiating the container, I make sure I add my 3 storeDescriptors to persistentStoreDescriptions (each representing an scheme)
let defaultDirectoryURL = NSPersistentContainer.defaultDirectoryURL()
var persistentStoreDescriptions: [NSPersistentStoreDescription] = []
let localStoreLocation = defaultDirectoryURL.appendingPathComponent("Local.sqlite")
let localStoreDescription = NSPersistentStoreDescription(url: localStoreLocation)
localStoreDescription.cloudKitContainerOptions = nil
localStoreDescription.configuration = "Local"
persistentStoreDescriptions.append(localStoreDescription)
let cloudStoreLocation = defaultDirectoryURL.appendingPathComponent("Cloud.sqlite")
let cloudStoreDescription = NSPersistentStoreDescription(url: cloudStoreLocation)
cloudStoreDescription.configuration = "iCloud"
cloudStoreDescription.cloudKitContainerOptions = "iCloud.com.xxx.yyy"
persistentStoreDescriptions.append(cloudStoreDescription)
let defaultStoreLocation = defaultDirectoryURL.appendingPathComponent("Default.sqlite")
let defaultStoreDescription = NSPersistentStoreDescription(url: defaultStoreLocation)
defaultStoreDescription.cloudKitContainerOptions = nil
defaultStoreDescription.configuration = "Default"
persistentStoreDescriptions.append(defaultStoreDescription)
container.persistentStoreDescriptions = persistentStoreDescriptions
Note: One important thing is to make sure that NSPersistentStoreDescription with the Default configuration is added last.
Secondly, I am for-eaching thought all data saved in core data checking if managedObject.objectID.persistentStore?.configurationName is "Default" (or any string containing Default. With my empiric implementation I got to the conclusion that configuration name might be different from case to case). If the above condition is true, create a new managedObject, I copy all properties from the old one to new one, delete the old managed object, and save the context.
for oldManagedObject in managedObjectRepository.getAll() {
guard let configurationName = oldManagedObject.objectID.persistentStore?.configurationName else {
continue
}
if (configurationName == "Default") {
let newManagedObject = managedObjectRepository.newManagedObject()
newManagedObject.uuid = oldManagedObject.uuid
newManagedObject.createDate = oldManagedObject.createDate
......
managedObjectRepository.delete(item: oldManagedObject)
managedObjectRepository.saveContext()
}
}
With this implementation, old data that was previously saved in Default.sqlite is now saved in Local.sqlite or 'Cloud.sqlite' (depending on which configuration contains which entity).

How to index and deindex NSUserActivities from Spotlight

I am trying to wrap my head around NSUserActivitys and I am not entirely sure on how to use them properly. I have setup my NSUserActivity properly like so:
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
attributeSet.title = "Title"
attributeSet.contentDescription = "Description"
let activity = NSUserActivity(activityType: ActivityType.activity.rawValue)
activity.persistentIdentifier = ActivityIdentifier.activity.rawValue
activity.title = "Title"
activity.requiredUserInfoKeys = ["Key"]
activity.userInfo = ["Key": data]
activity.isEligibleForSearch = true
activity.contentAttributeSet = attributeSet
self.userActivity = activity
self.userActivity!.becomeCurrent()
Now the activity gets indexed via the becomeCurrent() method. When I click on the activity in Spotlight everything works fine and the activity can be restored using the userInfo property.
But how do I delete the activity from Spotlight once it has be used (restored)? In this post the user recommends to use either deleteAllSavedUserActivities(completionHandler:) which works but I can't use since I don't want to delete all activities or deleteSavedUserActivities(withPersistentIdentifiers:completionHandler:) which does not work. For the first method the documentation says following however to the second method this does not apply:
Deletes all user activities stored by Core Spotlight...
Instead I could index the activities with the Core Spotlight API like so:
let item = CSSearchableItem(uniqueIdentifier: ActivityIdentifier.activity.rawValue, domainIdentifier: "DomainID", attributeSet: attributeSet)
CSSearchableIndex.default().indexSearchableItems([item]) { error in
if error != nil {
print(error!)
} else {
print("successfully indexed item")
}
}
and delete them with the deleteSearchableItems(withIdentifiers:completionHandler:) method. The problem with that is, I have to set the relatedUniqueIdentifier of my attributeSet and then the userInfo will be empty once I try to restore the activity (regarding post).
So what should I do, should I use both Core Spotlight and NSUserActivity and use CSSearchableItemAttributeSet to save the data instead of using the userInfo (why would apple to that?, why would they add the userInfo then?) or should I index my activity without Core Spotlight, but how do I delete the activity from Spotlight in this case?
There is just one thing I figured out: In the apple documentation for the domainIdentifier property of the CSSearchableAttributeSet it sounds like you are supposed to use this property to delete the NSUserActivity
Specify a domain identifier to group items together and to make it
easy to delete groups of items from the index. For example, to delete
a user activity, you can set this property on the contentAttributeSet
property of the NSUserActivity object and then call
deleteSearchableItems(withDomainIdentifiers:completionHandler:) on the
default().

Key value in AWSIoTDataManager

I am working with a project where I don't get it the value of
awsiotdatamanager is static string or it generated from AWS. I found it like this in sample code Also I read code this and this but I am getting more and more confused.
let AWSIoTDataManager = "MyIotDataManager"
let iotDataManager = AWSIoTDataManager(forKey: AWSIoTDataManager)
Thanks in advance :)

Creating UITabBarItem from an array

I'm writing an app that needs to create a number of tabs based on values from a database. It gets given a list of Pupils and Class numbers from a CSV file, and imports them into a database. I can run a SELECT DISTINCT on the database and I get a list of class numbers (eg -2, -1, R, 1, 2) and I can iterate through the list fine using
for class in classnumbers {
print (class[0])
}
and see a list of my class numbers, but I'm banging my head against a brick wall trying to generate the tabs at runtime. I've got the code:
let firstVc = UIViewController()
firstVc.title = "First"
firstVc.tabBarItem = UITabBarItem.init(title: "Home", image: UIImage(named: "HomeTab"), tag: 0)
tabBarCont.viewControllers = [firstVc]
This will create a tab when it loads like I expect, so I know that works, and I can add secondVC etc to create more... What I'm struggling with is using my class number instead of firstVC - so I'd like to be able to generate them like this:
for class in classnumbers {
let class[0]Vc = UIViewController()
class[0]Vc.title = "class[0]"
class[0]Vc.tabBarItem = UITabBarItem.init(title: "class[0]", image: UIImage(named: "class[0]"), tag: 0)
tabBarCont.viewControllers = [class[0]Vc]
}
and so on for each class in the database. I've seen the question answered with "use an array", but I can't see how that would be any different to looping round my database results, and I've seen "you can't do that with swift" - so is it do-able or do I need to come up with a different way of looking at the problem? (I won't know the class numbers / names that a school use until after getting their data - if I have to hard code them, I'd have to recompile the app for every different school?)

MongoEngine not saving embedded doc second time around

When I use MongoEngine to add an embedded doc to a doc, it works the first time when the list is empty but fails with subsequent tries saying: mongoengine.errors.OperationError: Could not save document (Cannot update 'sensorlist.1.alert_list.0._cls' and 'sensorlist.1.alert_list' at the same time)
The following test code demonstrates the issue: If you run it once you will see a new collection/document in foo that has an S2 embedded Sensor with qty 2 embedded Alerts. If you run it again it blows up - Any ideas?
Thx Bill
import mongoengine as ME
ME.connect('foo')
class Sensor(ME.EmbeddedDocument):
name = ME.StringField()
alert_list = ME.ListField()
class Alert(ME.EmbeddedDocument):
name = ME.StringField(default = 'new alert')
class SiteConfig(ME.Document):
siteid = ME.StringField()
sensorlist = ME.ListField(ME.EmbeddedDocumentField(Sensor))
if not SiteConfig.objects(siteid = '123456'):
newsite = SiteConfig(siteid = '123456')
newsite.save()
print("saved new site")
site = SiteConfig.objects(siteid = '123456').first()
newsensor = Sensor(name='S1')
site.sensorlist.append(newsensor)
site.save()
print("added sensor S1")
newsensor = Sensor(name='S2')
site.sensorlist.append(newsensor)
site.save()
print("added sensor S2")
for sensor in site.sensorlist:
if sensor.name =='S2':
alert = Alert()
sensor.alert_list.append(alert)
site.save()
print('added first alert to S2')
for sensor in site.sensorlist:
if sensor.name =='S2':
alert = Alert()
sensor.alert_list.append(alert)
site.save()
print('added second alert to S1')
I solved this by reproducing the code in MongoAlchemy which is very similar. That also failed but actually gave me useful exception info that pointed me to the problem which was that my Sensor class Alert_list definition needed to specify the class it contains.
So the fix is to define Sensor like this:
class Sensor(ME.DynamicEmbeddedDocument):
name = ME.StringField()
alert_list = ME.ListField(ME.EmbeddedDocumentField(Alert))
After all that pain over such a small issue I will probably stick with MongoAlchemy!