UI is not updating when loading Data - swift

During a loading-process I'm trying to update my UI (a label and a progress bar) from within a loop.
The UI is not updating though, until the loading-function is done.
The loading-function is not using another thread, I'm still in the main thread so from my understanding it should be possible to instantly-update the UI...
Is there something I am missing?
loading-function: (called from viewDidAppear)
func LoadDataFromDataCore(){
//1
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
//2
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "CDGlobeTile")
//3
do {
let DataCoreResult = try managedContext.fetch(fetchRequest)
var LabelFromCDArray = [LabelTile]()
for (index, CDGlobeTile) in DataCoreResult.enumerated() {
updateLoadingBar(Progress: Float(index) / Float(DataCoreResult.count - 1))
// "unpack" the data from CoreData...
}
do{
try managedContext.save()
} catch let error as NSError {
print("Error saving context. \(error), \(error.userInfo)")
}
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
print(" finished loading")
}
updateLoadingBar - function:
func updateLoadingBar(Progress: Float){
let percentage = Progress * 100
if let currentDispatch = OperationQueue.current?.underlyingQueue {
print(currentDispatch)
// this gives me "<OS_dispatch_queue_main: com.apple.main-thread>"
}
LoadingLabel.text = String(format: "%2.0f", percentage)
ProgressBar.progress = Progress
}

Related

How to save a string array to core data in swift

I'm trying to save a string array to core data, but every time I try to read the array a get an empty one(I'm using NSString because of an answer I've read here, but it doesn't seem to work). Here is the code I use for saving :
let i = UserDefaults.standard.integer(forKey: "Index")
let fetchRequest: NSFetchRequest<PersonalTask> = PersonalTask.fetchRequest()
do {
let tasks = try PersistanceService.context.fetch(fetchRequest)
self.personalTasks = tasks
} catch {
print("Error")
}
let pTask = personalTasks[i]
var subtasks = pTask.subtasks
print(subtasks as Any)
let subtask = textView!.text as NSString
subtasks?.append(subtask)
print(subtasks)
PersistanceService.saveContext()
Here is the function saveContext() :
static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
static var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Tasks")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("No descriptions found")
}
description.setOption(true as NSObject, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
// MARK: - Core Data Saving support
static func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
print("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
And here I am reading the data and getting an empty array :
let i = UserDefaults.standard.integer(forKey: "Index")
let fetchRequest: NSFetchRequest<PersonalTask> = PersonalTask.fetchRequest()
do {
let tasks = try PersistanceService.context.fetch(fetchRequest)
self.personalTasks = tasks
} catch {
print("Error")
}
let pTask = personalTasks[i]
print(pTask.title)
print(pTask.notes)
print(pTask.subtasks)
subtasks = pTask.subtasks as [String]? ?? [NSString]() as [String]
print(subtasks)
What should I do to fix this?

Using CoreData in swift from a tableview selection

I have 2 functions, one which should save the string to a CoreData attribute and another to fetch it. And this was written by copying working code from elsewhere in the project, so I can't figure out why when I print the contents of 'currentSetting' I get nil.
The save function is called from didSelectRowAtIndexPath and it sends in indexPath.row as the parameter and this fetches a String from an array.
func getImageSetting() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Settings")
do {
let fetcher = try context.fetch(fetchRequest)
let data = fetcher as? [NSManagedObject]
if let results = data {
let settingResult = results[0]
currentSetting = settingResult.value(forKey: "imageQuality") as? String
print(currentSetting as Any)
} else { print ("error") }
}
catch let error as NSError {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
func saveSetting(selection: Int) {
let coreSelection = choices[selection]
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.persistentContainer.viewContext
let itemEntity = NSEntityDescription.entity(forEntityName: "Settings", in: managedContext)!
let item = NSManagedObject(entity: itemEntity, insertInto: managedContext)
item.setValue(coreSelection, forKey: "imageQuality")
do {
try managedContext.save()
} catch let error as NSError {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}

Inserting data into a Core Data model is unacceptably slow

I need to be able to import over 100,000 records on a weekly basis. The data is coming from a web service as a CSV file. Downloading it is fast, as is massaging it into a usable form. However, adding the records to the model works but is unacceptably slow -almost an hour!
I realize I'm saving after each record. There must be a better way to do this.
Please advise or point me to another answer. Here is my working code. Many thanks.
func loadDataBase() {
for i in 1..<objectArray.count - 1 {
let item: [String] = objectArray[i]
s_stop_id = Int(item[0])
s_stop_code = Int(item[1])
s_stop_name = item[2]
let mainDelegate = UIApplication.shared.delegate as! AppDelegate
let context = mainDelegate.persistentContainer.viewContext
let newResource = NSEntityDescription.insertNewObject(forEntityName: stopEntity, into: context)
newResource.setValue(s_stop_id, forKey: "stop_id")
newResource.setValue(s_stop_name, forKey: "stop_name")
newResource.setValue(s_stop_code, forKey: "stop_code")
do {
try context.save()
} catch let error as NSError {
print("Error While Saving Data: \(error.userInfo)")
}
}
I'm showing some usage information. I appear to be using 100% CPU. Is it feasible to run this process in the background? Then timing won't be so much of an issue..
you should probably instantiate the context and save outside the for, it would be something like this:
func loadDataBase() {
let mainDelegate = UIApplication.shared.delegate as! AppDelegate
let context = mainDelegate.persistentContainer.viewContext
for i in 1..<objectArray.count - 1 {
let item: [String] = objectArray[i]
s_stop_id = Int(item[0])
s_stop_code = Int(item[1])
s_stop_name = item[2]
let newResource = NSEntityDescription.insertNewObject(forEntityName: stopEntity, into: context)
newResource.setValue(s_stop_id, forKey: "stop_id")
newResource.setValue(s_stop_name, forKey: "stop_name")
newResource.setValue(s_stop_code, forKey: "stop_code")
}
do {
try context.save()
} catch let error as NSError {
print("Error While Saving Data: \(error.userInfo)")
}
}
Test the following with autoreleasepool.
let mainDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newResource = NSEntityDescription.insertNewObject(forEntityName: stopEntity, into: context)
for i in 1..<objectArray.count - 1 {
autoreleasepool(invoking: { () -> () in
let item: [String] = self.objectArray[i]
s_stop_id = Int(item[0])
s_stop_code = Int(item[1])
s_stop_name = item[2]
newResource.setValue(s_stop_id, forKey: "stop_id")
newResource.setValue(s_stop_name, forKey: "stop_name")
newResource.setValue(s_stop_code, forKey: "stop_code")
do {
try context.save()
} catch let error as NSError {
print("Error While Saving Data: \(error.userInfo)")
}
})
}

Proper singleton class to use CoreData

I'm trying to create a singleton class which works with an NSManagedObjectContext.
This is the class:
import Foundation
import CoreData
class PersistenceService{
init(){}
// MARK: - Core Data stack
static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "frazeit")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
static func saveContext () {
let mainContext = persistentContainer.viewContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = mainContext
privateContext.perform {
if privateContext.hasChanges {
do {
try privateContext.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
}
In some occasion, it does not push changes into the persistent store, while the app is open the persistent container is changed but when I re-run the app changes are gone. What's the right way to save the changes into the persistent store.
This the class that does not work properly:
class func add(word: String, quotes:[Quotes], language: String){
for item in quotes {
if let phrase = item.phrase, let author = item.author {
let quote = CachedQuotes(context: PersistenceService.context)
quote.phrase = phrase
quote.date = Date() as NSDate
quote.keyword = word
quote.language = language
quote.author = author
PersistenceService.saveContext()
}
}
}
I call it to save quotes which are fetched from the network:
override func viewDidLoad() {
let quotes = CachedQuotes.getAllQuotes()
//Prints the number of saved records which is 0 now
self.getQuote { (result, error) in
if let qoutes = result?.quotes {
CachedQuotes.add(word: "friend", quotes: qoutes, language: "en")
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let quotes = CachedQuotes.getAllQuotes()
//Prints the number of saved records which is 10 now
}
But when I re-run the app, nothing is saved into the persistance container.
UPDATE:
The code below works now
static func saveContext () {
let mainContext = persistentContainer.viewContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.automaticallyMergesChangesFromParent = true
privateContext.parent = mainContext
privateContext.perform {
do {
try privateContext.save()
mainContext.perform({
do {
try mainContext.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
})
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
First it saves the private quoue then saves the main.
let mainContext = persistentContainer.viewContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = mainContext
You edit a context and then save the same context to persist the changes. Creating a child context to .viewContext and saving said child context does not save the .viewContext itself, where you made changes.
If you want to use background queues, first set var automaticallyMergesChangesFromParent: Bool on the .viewContext where you want to receive changes from the background queue. Then you create a background context, set on it the same persistentStoreCoordinator from .viewContext, make changes on it and then save the background queue.
Using privateContext.perform is a good start. You can do better if you wrap the changes to quote in a perform through the context in which the quote was created in the first place, so you access quote through the same thread the context uses.
Here is the singleton from Apple's Refreshing and Maintaining Your App Using Background Tasks sample.
import Foundation
import CoreData
class PersistentContainer: NSPersistentContainer {
private static let lastCleanedKey = "lastCleaned"
static let shared: PersistentContainer = {
ValueTransformer.setValueTransformer(ColorTransformer(), forName: NSValueTransformerName(rawValue: String(describing: ColorTransformer.self)))
let container = PersistentContainer(name: "ColorFeed")
container.loadPersistentStores { (desc, error) in
if let error = error {
fatalError("Unresolved error \(error)")
}
print("Successfully loaded persistent store at: \(desc.url?.description ?? "nil")")
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyStoreTrumpMergePolicyType)
return container
}()
var lastCleaned: Date? {
get {
return UserDefaults.standard.object(forKey: PersistentContainer.lastCleanedKey) as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: PersistentContainer.lastCleanedKey)
}
}
override func newBackgroundContext() -> NSManagedObjectContext {
let backgroundContext = super.newBackgroundContext()
backgroundContext.automaticallyMergesChangesFromParent = true
backgroundContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyStoreTrumpMergePolicyType)
return backgroundContext
}
}
Personally I prefer passing the NSPersistentContainer around via dependency injection but it requires a lot more effort.

Sync Core Data with server - same objects added multiple times

I have a list of categories that my user can choose. Since I am planning to add new categories without having to send a new update, I would like to load the new categories from my server every time a user launch the app. I came up with this code but every time it runs it keeps adding the same categories.
func loadDealCategory(){
let myUrl = NSURL(string: "\(ipAddress)/v1.0/dealCategory.php")
let request = NSMutableURLRequest(URL: myUrl!)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{ data, response, error in
if error != nil {
print("error\(error)")
}else{
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSArray
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
for jsonDictionary in json {
// listing of columns in JSON seed file
let categoryDescription = jsonDictionary.valueForKey("category_description") as! String
print(categoryDescription)
let newCategory = NSEntityDescription.insertNewObjectForEntityForName("Categories", inManagedObjectContext: managedContext)
newCategory.setValue(categoryDescription, forKey: "category_description")
// I tried also format: "category_description = %#"
let fetchRequest = NSFetchRequest(entityName: "Categories")
fetchRequest.predicate = NSPredicate(format: "category_description LIKE %#", categoryDescription)
do {
let fetchResults = try managedContext.executeFetchRequest(fetchRequest)
print(fetchResults)
if fetchResults.count == 0{
do {
try managedContext.save()
//5
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}else{
print("\(categoryDescription) already exist!")
}
} catch let error as NSError {
// failure
print("Fetch failed: \(error.localizedDescription)")
}
}
} catch{
print("something went wrong loading json")
}
}
}
task.resume()
}
is it ok , loading it in func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
Found the problem, I was inserting the object before checking if it was already in the entity.
if fetchResults.count == 0{
let newCategory = NSEntityDescription.insertNewObjectForEntityForName("Categories", inManagedObjectContext: managedContext)
newCategory.setValue(categoryDescription, forKey: "category_description")
}else{
print("Could not save \(error), \(error.userInfo)")
}
and only then save
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}