Core data does not save the data properly - swift

EDITED
I have a UITableView which displays multiple social media posts. I use the Prefetch Source Delegate for prefetching Posts and I use Core Data to be stored after being fetched from web server. Problem that I have is that I get no error, but the data does not stay saved between launches in CoreData.
Snip of code
func configureDataSourceTable(){
self.dataSource = UITableViewDiffableDataSource<String,String>(tableView: self.table_view){
(tableView, indexPath, ref) -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CheckInCell
if !self.postData.currentPostFetching(ref: ref){
self.postData.fetchPost(ref: ref) { post in
DispatchQueue.main.async {
self.setPostNeedsUpdate(ref: ref)//reconfigure items
}
return cell
}
func fetchPost(ref: String,completion : (PostStruct)->Void) {
if self.dataContainer.postIsPartOfCoreData(ref: ref){
var postStruct = self.dataContainer.getPostStructFromDB(ref: ref)
completion(postStruct)
}else{ //has to be downloaded from web server
Task {
let post = await getPostFromServer()//not relevant in understanding problem
var postStruct = self.convertDataToPost(post)
completion(postStruct)
self.dataContainer.savePostStruct(post: postStruct)
}
}
}
Class of the DataContainer subclass of NsPersistentStore
func savePostStruct(post_struct : PostStruct,image_data : String){
Task.detached {
var postObject = PostCore.init(context : self.viewContext)
postObject.name = "Test"
var image = ImageCore.init(context: self.viewContext)
imageObject.data = image_data
postObject.image = imageObject
do {
if Thread.isMainThread {
print("Running on the main thread parent")
}else {
print("Other thread")
}
try self.viewContext.save()
print("Reference \(post_struct.ref) child saved")
}catch {
print("Error that produced \(post.ref) catched while trying to savethe data on child: \(error.localizedDescription),number : \(error)");
}
if post_struct.ref == "AAAA"{
var post = PostCore.fetchRequest()
var predicate = NSPredicate.init(format: "ref like %#", post.ref!)
fetchCheckIn.predicate = predicate
fetchCheckIn.fetchLimit = 1
fetchCheckIn.includesPropertyValues = true
let result = try! self.viewContext.fetch(fetchCheckIn)
print(result)
//This line returns the Object
print(self. checkPostExistHasPictures(refPost: post.ref))
//This is the line where always come (false, false) meaning that the Post is not saved
}
}
func getPostStructFromDB(refPost : String)async->PostStruct {
return await self.viewContext.perform{
var fetchPost = PostCore.fetchRequest()
var predicate = NSPredicate.init(format: "ref like %#", refPost)
fetchPost.predicate = predicate
fetchPost.fetchLimit = 1
fetchPost.includesSubentities = true
fetchPost.returnsObjectsAsFaults = false
fetchPost.includesPropertyValues = true
let result = try? self.viewContext.fetch(fetchCheckIn)
var refPost = result.first.ref
return PostStruct(ref : ref, image: refPost.image.data)
}
}
}
func checkPostExistHasPictures(refPost : String)->(Bool,Bool){
var fetchCheckIn = CheckInCore.fetchRequest()
var predicate = NSPredicate.init(format: "ref like %#", refPost)
fetchCheckIn.predicate = predicate
fetchCheckIn.fetchLimit = 1
var exist = false
var hasPicture = false
self.viewContext.performAndWait {
do{
let result = try? self.viewContext.fetch(fetchCheckIn)
if result?.first == nil {
}else {
print("Exists with reference \(reference_checkin)")
if result!.first!.pic_ref == nil {
exist = true
hasPicture = false
}else if result!.first!.image!.count != 0 {
exist = true
hasPicture = true
}
}
}catch {
print("error catched")
}
}
return(exist, hasPicture)
}
}
Relation between PostCore and ImageCore is zero to many.
I don't get any error code or error message. I commented the line where I get the error. I have tried all possible ways using a backGroundContext each time a save is made to not block the main thread and still is the same problem.

Your code is extremely confusing.
Why have you got Task scattered everywhere, you are doing no async/await work?
Your savePostStruct method takes a parameter called post which contains your data, then you immediately replace it with a value of the same name of type PostCore, which is presumably a managed object, then you only set one value on it.
Then, when you come to fetch an item, there's nothing there, because you haven't written anything to the managed object besides the name "Test".
At the very least, you have to change this line:
var post = PostCore.init(context : self.viewContext)
To
let postObject = PostCore(context: self.viewContext)
Then you won't get confused between the managed object and the struct you're passing in.
You are also saving the context before you've written any of the values to it.

Related

reload table with async search result

I large amount of data in my app with search functionality. I am using SQLite and Core Data to search and Fetch data.
Here is my search function,
func fetchSearchResultsWith(_ searchText : String?){
DispatchQueue.global(qos: .background).async {
var resArr : [Int64] = []
let stmt = "SELECT rowid FROM htmlText_fts WHERE htmlText MATCH '\(searchText!)*'"
do {
let res = try self.db.run(stmt)
for row in res {
resArr.append(row[0] as! Int64)
}
} catch {
print(error.localizedDescription)
}
let request : NSFetchRequest<Monos> = Monos.fetchRequest()
request.fetchLimit = 200
let predicate = NSPredicate(format: "id in %#", resArr)
request.predicate = predicate
var arr : [Items]? = []
do {
arr = try context.fetch(request)
} catch {
print(error.localizedDescription)
}
DispatchQueue.main.async(execute: {
self.monosSearchResult = arr
self.tableView.reloadData()
})
}
}
I am using DispatchQueue.global.async to avoid freezing UI, but then its returning async array and my table view ends up reloading with wrong result. If I use DispatchQueue.global.sync it works fine, but then my UI freezes when I type in to searchBar. I am not sure what I can do get right result. Any help will be appreciated!
Please let me know if you need any further information.
Since you have a 2 step search mechanism , a new search may be initiated before the other ones end , so to lightWeight this operation , store the last value of the textfield inside a var
lastSear = textfield.text
fetchSearchResultsWith(lastSear)
then do this inside the search function in 3 places
Before search the DB & after & before setting the array and reloading the table
if searchText != lastSear { return }
You have not included your table data source methods which populate the table, but I assume you are using values from self.monosSearchResult. If not, then your fetch code is populating the wrong values, and that may be part of your problem.
Additionally, your fetch request needs to be running on the appropriate thread for your NSManagedObjectContext, not necessarily (probably not) the global background queue. NSManagedObjectContext provides the perform and performAndWait methods for you to use their queues properly.
func fetchSearchResultsWith(_ searchText : String?){
// context: NSManagedObjectContext, presumably defined in this scope already
// since you use it below for the fetch.
// CHANGE THIS
// DispatchQueue.global(qos: .background).async {
// TO THIS
context.perform { // run block asynchronously on the context queue
var resArr : [Int64] = []
let stmt = "SELECT rowid FROM htmlText_fts WHERE htmlText MATCH '\(searchText!)*'"
do {
let res = try self.db.run(stmt)
for row in res {
resArr.append(row[0] as! Int64)
}
} catch {
print(error.localizedDescription)
}
let request : NSFetchRequest<Monos> = Monos.fetchRequest()
request.fetchLimit = 200
let predicate = NSPredicate(format: "id in %#", resArr)
request.predicate = predicate
var arr : [Items]? = []
do {
arr = try context.fetch(request)
} catch {
print(error.localizedDescription)
}
DispatchQueue.main.async(execute: {
self.monosSearchResult = arr
self.tableView.reloadData()
})
}
}

swift element is empty

I am trying to get the first or current exercise from my core data but swift keeps telling me that the element is empty. When i run the app and set the break points the debugger shows that the element is empty but no errors. here are the functions i am using to get the element data.
func currentWorkout() -> Workout? {
let client = currentClient()
return (appointment?.workouts as? Set<Workout>)?.first(where: { $0.client == client })
}
private func currentCard() -> Card? {
return currentWorkout()?.card
}
private func currentClientPlannedExercises() -> [ExerciseInfo] {
if let currentCard = currentCard(), let template = currentCard.template, let exerciseSets = template.exerciseSets?.array as? [ExerciseSet] {
let numCardsWithThis = (template.cardsWithThisTemplate as? Set<Card>)?.filter { $0.client != currentClient() }.count ?? 0
let exercsiseSetNumber = numCardsWithThis % exerciseSets.count
if let result = exerciseSets[exercsiseSetNumber].exercises?.array as? [ExerciseInfo] {
return result
}
}
return [ExerciseInfo]()
}
private func currentExercise() -> Exercise? {
// we can't have an exercise without a selection
guard let selectedExercise = currentExerciseInfo(), let currentCard = currentCard() else{
return nil
}
// get the first exercise on the current card that has the same exercise info as the one selected
if let exercises = currentWorkout()?.exercises as? Set<Exercise>{
return exercises.first(where: { $0.exerciseInfo == selectedExercise })
}
let exercise = Exercise(context: context)
exercise.workout = currentWorkout()
exercise.exerciseInfo = selectedExercise
//TODO: Set Seat
return exercise
}
private func currentExerciseInfo() -> ExerciseInfo? {
guard let selectedRow = exercisesTableView.indexPathForSelectedRow else {
return nil
}
return currentClientPlannedExercises()[selectedRow.row]
}
if the Issue is fetching then You can use this Code:
For Fetching the data from Core Data
var tasks: [Task] = [] //Your Entity Name in Bracket
func getData() {
do {
tasks = try context.fetch(Task.fetchRequest()) //Instead of Task your Entity Name
} catch {
print("Fetching Failed")
}
}
And use it like:
for data in tasks{
print(data.name) // All the Attributes name after data.attributename
print(data.address)
}
If it is in tableView:
let data = tasks[indexPath.row]
print(data.name)
You will get the data if it is there.
Edit to Check if Data entered or not
Print the Path like this:
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
print(paths[0])
Go to sqlite file and open and check if there is Data or not inside that.
Edit If you are facing the issue in Adding Data to Core Data
Simple code to add Data
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Task(context: context) //Entity Name here instead of Task
task.name = taskTextField.text! // Attribute name here after task.attributename
// Save the data to coredata
(UIApplication.shared.delegate as! AppDelegate).saveContext()
Hope this help.
I found the issue was in the currentExercise function it wasn't calling the first exercise until the it had an exercise. I fixed by rewriting the function
private func currentExercise() -> Exercise? {
// we can't have an exercise without a selection
guard let selectedExercise = currentExerciseInfo() else{
return nil
}
// get the first exercise on the current card that has the same exercise info as the one selected
if let exercises = currentWorkout()?.exercises as? Set<Exercise>, let firstExercise = exercises.first(where: { $0.exerciseInfo == selectedExercise }) {
return firstExercise
}
let exercise = Exercise(context: context)
exercise.workout = currentWorkout()
exercise.exerciseInfo = selectedExercise
//TODO: Set Seat
return exercise
}

How to work with Firebase Asynchronously? Database reading giving odd results

I have written the following function to search through my Firebase database and I have also looked into using debug statements and tested with breakpoints to see this function is pulling the correct data and it is. But when I return the array at the end, the array is empty. As far as I understand this is due to the asynchronous nature of firebase. The function is getting to the end before the data is being added to the array. How do I fix this so it can work as intended, I want to return an array of items which I can then use for other functions.
static func SearchPostsByTags(tags: [String]) -> [Post]{
var result = [Post]()
let dbref = FIRDatabase.database().reference().child("posts")
dbref.observeSingleEvent(of: .value, with: { snap in
let comps = snap.value as! [String : AnyObject]
for(_, value) in comps {
let rawTags = value["tags"] as? NSArray
let compTags = rawTags as? [String]
if compTags != nil {
for cTag in compTags! {
for tag in tags {
if (tag == cTag) {
let foundPost = Post()
foundPost.postID = value["postID"] as! String
foundPost.title = value["title"] as! String
result.append(foundPost)
}
}
}
}
}
})
return result
}
}
You are returning your array before the async call ends. You should fill your array inside the async call and call then another method, which provides the results.
static func SearchPostsByTags(tags: [String]) {
let dbref = FIRDatabase.database().reference().child("posts")
dbref.observeSingleEvent(of: .value, with: { snap in
let comps = snap.value as! [String : AnyObject]
var result = [Post]()
for(_, value) in comps {
let rawTags = value["tags"] as? NSArray
let compTags = rawTags as? [String]
if compTags != nil {
for cTag in compTags! {
for tag in tags {
if (tag == cTag) {
let foundPost = Post()
foundPost.postID = value["postID"] as! String
foundPost.title = value["title"] as! String
result.append(foundPost)
}
}
}
}
}
// Call some func to deliver the finished result array
// You can also work with completion handlers - if you want to try have a look at callbacks / completion handler section of apples documentation
provideTheFinishedArr(result)
})
}

DynamoDB scan : Does not return correct values

I have written the following function but I have a problem with its returning value.
In my console I can see the results pulled from AWS dynamoDB, I can even display it. As you can see, I am printing item variable.
I instantiated an array in my function and append each item pulled from AWS to it but it returning nil.
Can you please see what I miss in my code thanks.
func scanClientList(_ startFromBeginning: Bool) -> [Client]{
var clients = [Client]()
UIApplication.shared.isNetworkActivityIndicatorVisible = true
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
let queryExpression = AWSDynamoDBScanExpression()
//queryExpression.exclusiveStartKey = self.userId
queryExpression.limit = 20
dynamoDBObjectMapper.scan(Client.self, expression: queryExpression).continueWith(executor: AWSExecutor.mainThread(), block: { (task:AWSTask!) -> AnyObject! in
if let paginatedOutput = task.result {
for item in paginatedOutput.items as! [Client] {
print("ITEMS: \(item)")
clients.append(item)
}
if paginatedOutput.lastEvaluatedKey == nil {
}
}
UIApplication.shared.isNetworkActivityIndicatorVisible = false
if let error = task.error as? NSError {
print("Error: \(error)")
}
return nil
})
return clients
}
Actually my function here works perfectly, I didn't initialize the variable clients in the functions
I had something like
var clients = [Client]()?
and changed it to
var clients:Array<Client> = []

Cannot convert value of type 'String?!' to expected argument type 'Notifications'

I am trying to check the id of a record before I put it into the array, using xcode swift
here is the code. But, i get the following error
Notifications.swift:50:46: Cannot convert value of type 'String?!' to expected argument type 'Notifications'
on this line
*if (readRecordCoreData(result["MessageID"])==false)*
Please can some one help to explain this error
import CoreData
struct Notifications{
var NotifyID = [NSManagedObject]()
let MessageDesc: String
let Messageid: String
init(MessageDesc: String, Messageid:String) {
self.MessageDesc = MessageDesc
self.Messageid = Messageid
// self.MessageDate = MessageDate
}
static func MessagesWithJSON(results: NSArray) -> [Notifications] {
// Create an empty array of Albums to append to from this list
var Notification = [Notifications]()
// Store the results in our table data array
if results.count>0 {
for result in results {
//get fields from json
let Messageid = result["MessageID"] as! String
let MessageDesc = result["MessageDesc"] as? String
let newMessages = Notifications(MessageDesc: MessageDesc!, Messageid:Messageid)
//check with id's from core data
if (readRecordCoreData(result["MessageID"])==false)
{
Notification.append(newMessages)
}
}
}
return Notification
}
//check id
func readRecordCoreData(Jsonid: String) -> Bool {
var idStaus = false
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
//2
let fetchRequest = NSFetchRequest(entityName: "ItemLog")
//3
do {
let resultsCD = try! managedContext.executeFetchRequest(fetchRequest)
if (resultsCD.count > 0) {
for i in 0 ..< resultsCD.count {
let match = resultsCD[i] as! NSManagedObject
let id = match.valueForKey("notificationID") as! String
if (Jsonid as String! == id)
{
idStaus = true
}
else{
idStaus = false
}
}
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
return idStaus
}
One of your methods is static and the other one is not :
func readRecordCoreData(Jsonid: String) -> Bool
static func MessagesWithJSON(results: NSArray) -> [Notifications]
Depending on what you want to accomplish you could declare both static, none, or replace
//check with id's from core data
if (readRecordCoreData(result["MessageID"])==false)
{
Notification.append(newMessages)
}
By
//check with id's from core data
if (Notifications.readRecordCoreData(Messageid)==false)
{
Notification.append(newMessages)
}
Not sure if the code will work past compilation however as there are many readability issues