how to save mixed sort? - swift

From the list in which the words are located, I need to display them in a mixed state.
To do this, I added a shuffle sort field to the entity
func setShuffleWords(id: UUID?) -> [CoreWord]? {
guard let id = id else { return nil }
let requestWords = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreWord")
requestWords.predicate = NSPredicate(format: "ANY dictionary.id == %#", id as CVarArg)
var coreWords: [CoreWord]?
if let result = try? managedContext.fetch(requestWords) as? [CoreWord] {
let words = result.shuffled()
for (index, value) in words.enumerated() {
if value.value(forKey: "shuffle") != nil {
value.setValue(index, forKey: "shuffle")
}
}
coreWords = words
}
save()
return coreWords
}
func getShuffleWords(id: UUID?) -> [CoreWord]? {
guard let id = id else { return nil }
let requestWords = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreWord")
let sort = NSSortDescriptor(key: "shuffle", ascending: true)
requestWords.predicate = NSPredicate(format: "ANY dictionary.id == %#", id as CVarArg)
requestWords.sortDescriptors = [sort]
do {
let result = try managedContext.fetch(requestWords)
return result as? [CoreWord]
} catch let error {
fatalError(error.localizedDescription)
}
}
But, the problem is that if I mix up another list where this word is, then the value is rewritten and it will change its position everywhere.
How to keep mixed order in a specific list?

In this situation you need to replace your many-many relationship with an intermediate entity. Let’s call that entity a Listing. It will represent the listing of a particular word in a particular group, so each Listing should have a to-one relationship to CoreWord and a to-one relationship to CoreGroup. In each case the inverse (which you might call “listings”) should be to-many. The Listing entity should have (at least) one attribute, shuffle, which represents the sequence of a particular word on a particular group.
Life generally gets more complicated, because you have to create and manage these intermediate objects rather than just working the Word and Group objects and their relationships.

Related

sum only values with specified bool with core data

I have a tableview with values. The database is made with core data. You can set the values to true or false. I only want to sum the values with true. To sum the values i have this code.
func printData() {
//Shorthand Argument Names
//let request: NSFetchRequest<Gegenstand> = Gegenstand.fetchRequest()
//let records = try! context.fetch(request) as [NSManagedObject]
let sum = ViewController.liste.reduce(0) { $0 + ($1.value(forKey: "gewicht") as? Double ?? 0) }
print("Gesamtgewicht: \(sum) kg")
gewicht = sum
if gewicht > 3500 {
gewichtLabel.textColor = .red
gewichtLabel.text = "\(gewicht) kg"
}
}
I tried it with an if-function but i don't know to use it with core data.
Create a coreData fetchRequest with isValue=true,
Calculate the sum of return fetchRequest
func fetchAllWithTrue() -> [Gegenstand] {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: Gegenstand)
fetchRequest.predicate = NSPredicate(format: “isValue== YES")
do {
let fetchedObjects = try coreDataManager.context.fetch(fetchRequest) as? [Gegenstand]
return fetchedObjects
} catch {
print(error.localizedDescription)
return [Gegenstand]()
}
}
You can do all of it in Core Data if you want. Filter for true filtering with an NSPredicate, and have Core Data calculate the sum using NSExpression.
First set up the fetch request to get only entries where the property is true and make sure it returns dictionary-type results (I don't know what your boolean is called, so here I'm calling it flag. Put your property name there):
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Gegenstand")
fetchRequest.resultType = .dictionaryResultType
fetchRequest.predicate = NSPredicate(format: "flag = true")
Then set up an NSExpressionDescription that can get the sum of gewicht values:
let sumExpression = NSExpression(format: "sum:(gewicht)")
let sumExpressionDescription = NSExpressionDescription()
sumExpressionDescription.expression = sumExpression
sumExpressionDescription.resultType = .double
sumExpressionDescription.name = "gewichtSum"
What this does is create a new kind of property that Core Data understands where the value is the sum the values of gewicht and is named gewichtSum. Fetch requests know how to use that kind of property. You do it like this:
fetchRequest.propertiesToFetch = [ sumExpressionDescription ]
Since the fetch uses dictionaryResultType, running the fetch returns an array of dictionaries. It's an array because fetch always returns an array, but here there's only one entry. The dictionary in that array entry has a key called gewichtSum for a Double property that was calculated from the expression above. You get the value like this:
do {
let result = try context.fetch(fetchRequest)
if result.count > 0,
let sumInfo = result[0] as? [String:Double],
let gewichtSum: Double = sumInfo["gewichtSum"] {
print("Sum: \(gewichtSum)")
}
} catch {
...
}
The print statement above prints the sum of all gewicht values where flag is true. It only includes true values for flag because of the NSPredicate, and it contains the sum because of the expression description.

How does case insensitive sort work in Swift or other languages?

So, I am fetching some data from CoreData and I am using NSSortDescriptor to sort it in ascending order (I've tried other methods too). What happens is that the lowercase string with same text comes first. As per my understanding, in ASCII, uppercase strings come first ('A' starting at 65) and then lowercase strings ('a' starting at 97) and if we consider this, uppercase strings should come first when sorting in ascending order.
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true, selector: #selector(NSString.caseInsensitiveCompare(_:)))
let entities = CoreDataManager.getData(entityName: "Entity", predicate: nil, sortDescriptor: sortDescriptor) as? [Entity]
In my CoreDataManager class, I have the following methods.
class CoreDataManager: NSObject {
static func getManagedObject() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.managedObjectContext
}
static func getData(entityName: String, predicate: NSPredicate? = nil, sortDescriptor: NSSortDescriptor? = nil) -> [NSManagedObject] {
var resultsManagedObject: [NSManagedObject] = []
do {
let managedObject = getManagedObject()
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
if let descriptor = sortDescriptor {
request.sortDescriptors = [descriptor]
}
if predicate != nil {
request.predicate = predicate
}
let results = try managedObject.fetch(request)
resultsManagedObject = results as! [NSManagedObject]
} catch {
print("There was an error retrieving data")
}
return resultsManagedObject
}
}
I've also tried sorting using sorted method of arrays but that was also giving the same output as using NSSortDescriptor.
let arr = ["bamboo", "document", "BAMBOO", "DOCUMENT"]
print(arr.sorted { $0.lowercased() < $1.lowercased() }) // Output ~> ["bamboo", "BAMBOO", "document", "DOCUMENT"]
Why is sorting working like this? Is it the same in other languages?
It's not sorting lowercase first. It's comparing uppercase and lowercase as equal. But obviously, in an array, something has to come first. If a sorting algorithm is "stable", then if two elements compare equal, the one that appears first in the input will also appear first in the output. If the algorithm is "unstable" they might come out in any random order. It looks like you're getting a stable sort (or just getting lucky); since your example input has lowercase values before their corresponding uppercase values, the output does too.

Swift: Core data trouble with to-many relationship and mutableOrderedSetValueForKey

I'm having trouble understanding why my ordered set is only saving 1 entry.
my code is as follows:
let newInovice = NSEntityDescription.insertNewObjectForEntityForName("Invoice", inManagedObjectContext: managedObjectContext) as! InvoiceMO
let itemDetail = NSEntityDescription.insertNewObjectForEntityForName("InvoiceDetail", inManagedObjectContext: managedObjectContext) as! InvoiceDetailMO
// Data Entry
// Add invoice number to Invoice
newInovice.invoiceNumber = getInvoiceNum()
newInovice.date = invoiceDate
// Create mutable set to add details
for items in details {
itemDetail.detail = items[PropKeys.itemDescription]
let item = items[PropKeys.itemPrice] ?? "0"
let itemDouble = { return Double(item) ?? 0 }()
itemDetail.price = itemDouble
let quantity = items[PropKeys.itemQuantity] ?? "1"
let quantityInt = { return Int(quantity) ?? 0 }()
itemDetail.quantity = quantityInt
print("This is the item detail before insertion: \(itemDetail)")
newInovice.mutableOrderedSetValueForKey("invoiceDetails").addObject(itemDetail)
subtotal += itemDouble * Double(quantityInt)
}
print("details.Count = \(details.count)")
print("Start Mutable \(newInovice.invoiceDetails?.count) End Mutable Set")
// Save the Data
do {
try newCustomer.managedObjectContext?.save()
} catch {
print(error)
}
I've read through the docs and it seems like I'm doing this correctly but it only adds one entry to the ordered set even though I've iterated through the array of objects.
Here is my debugger showing the entries before they are added and the count of the managed object after adding the objects.
debuggerWindow
my relationships are set up as follows:
relationships
You are only creating one InvoiceDetail outside your for loop. Obviously, you will have to create one for each detail that is contained in the data array.

Swift - How to save entries to core data in a loop

I have 2 entities: Series and Season. A Series can have multiple seasons, so I set the relationship type to "to many". season_counter is an Int Array, containing the amount of episodes.
let newSeries = NSEntityDescription.insertNewObjectForEntityForName("Series", inManagedObjectContext: self.context!) as! Series
for var i = 0; i < season_counter.count; ++i{
let newSeason = NSEntityDescription.insertNewObjectForEntityForName("Season", inManagedObjectContext: self.context!) as! Season
newSeason.value = i+1
newSeason.episodes = season_counter[i]
newSeries.setValue(NSSet(object: newSeason), forKey: "seasons")
do {
try context?.save()
} catch _ {
}
}
While debugging I noticed, that season_counter stores the correct values. When I display the results, I have only the last season stored (for example 13 episodes, seasons.count is 1):
do {
let fetchRequest = NSFetchRequest(entityName: "Season")
fetchRequest.predicate = NSPredicate(format: "series = %#", series)
try seasons = context?.executeFetchRequest(fetchRequest) as! [Season]
print(seasons.count)
print(seasons[0].episodes)
} catch {
}
Any tips to solve this?
In each pass through your loop, you're throwing away the previous value of seasons and replacing it with a set that contains only one season (the newly created newSeason):
newSeries.setValue(NSSet(object: newSeason), forKey: "seasons")
Instead of doing that, build up a set of all of the newSeason instances, and then call setValue:forKey: once, with that set.
Your life will be easier, by the way, if you create subclasses of NSManagedObject and get out of the "value for key" business.
newSeries.setValue(NSSet(object: newSeason), forKey: "seasons")
With this line you always create a new set of seasons containing exaclty one Season object - newSeason.
Try to do that:
var prevSeasons = newSeries.valueForKey("seasons")
prevSeasons.insert(newSeason)
newSeries.setValue(prevSeasons, forKey: "seasons")

Core data Fetch request on relationship properties

My app has a number of wordLists each of which contain a number of words. In one view controller a tableView lists the wordLists and then a sub view controller has a tableView with the words in it. The segue passes the wordList entity. But I cannot work out how to do a fetch request on the wordList passed to then get all the words. Error message is shown below. I need to do a fetch request rather than looking at properties so I can do a sort.
Thanks in advance for any help on this one....
The WordList entity has an attribute listName, and relationships: words, destination: Word, Inverse: wordList
The Word entity has an attribute wordName, wordIndex and relationships: wordList, destination: WordList, Inverse: words
My WordsViewController looks like:
class WordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
var coreDataStack: CoreDataStack = (UIApplication.sharedApplication().delegate as! AppDelegate).coreDataStack
var wordList: WordList?
var words = [Word]()
var word: Word?
override func viewDidLoad() {
super.viewDidLoad()
// set the title of the scene
title = wordList?.listName
// fetch all the words that are part of the wordList passed to this VC.
let fetchRequest = NSFetchRequest(entityName: "Word")
let wordListPredicate = NSPredicate(format: "word.wordList == '\(wordList)'") // GIVES AN ERROR SAYING UNABLE TO PARSE THE FORMAT STRING "word.wordList == 'Optional(<WordList:0x...
let sortDescriptor = NSSortDescriptor(key: "wordIndex", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = wordListPredicate
do {
if let results = try coreDataStack.managedObjectContext.executeFetchRequest(fetchRequest) as? [Word] {
words = results
}
} catch {
fatalError("There was an error fetching system person")
}
}
You cannot use string interpolation to build a predicate, use
argument substitution instead. In your case (since var wordList: WordList? is an
optional):
if let theWordlist = wordlist {
let wordListPredicate = NSPredicate(format: "wordList == %#", theWordlist)
} else {
// wordlist is nil ...
}
Note also that "word.wordList == " in the predicate should be
"wordList == ", because "wordList" is the property of the "Word"
entity.
For more information, see Predicate Format String Syntax
in the "Predicate Programming Guide".
You should use a NSFetchedResultsController, not fetch all data in viewDidLoad. The fetched results controller will help you fetch and display the data efficiently, react to changes, optimize memory, etc.
In the fetched results controller's fetch request (which fetches entity Word), use this predicate (with a safeguard if your optional wordList is nil):
request.predicate = wordList != nil ?
NSPredicate(format: "wordlist = %#", wordList!) :
NSPredicate(value: false)
This assumes that there is a one-to-many relationship between word list and its words. I would recommend this setup, even if it implies that you might have to store the same word more than once if it is in different lists.
NB: the false predicate ensures you get a valid fetch request which will fetch nothing.