I have Core Data database. Structure is on the image (DBContact have many DBMessages with property messages, each DBMessage have one contact with property contact):
I want to display messages grouped by contact and sorted by date. So the number of rows have to be = number of contacts with messages. And in each row I have to display avatar, nameAndSurname of contact and text and date of last message of this contact.
The only solution I found is:
let entityName = kDBOrder
let entityDescription = NSEntityDescription.entityForName(entityName, inManagedObjectContext: managedObjectContext)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entityDescription
fetchRequest.fetchBatchSize = 20
// sort
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "contact.id", ascending: false), NSSortDescriptor(key: "date", ascending: false)]
let sectionNameKeyPath: String? = "contact.id"
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: sectionNameKeyPath, cacheName: nil)
fetchedResultsController!.delegate = self
if tableViewMain != nil {
do {
try fetchedResultsController!.performFetch()
} catch {
print("An error occurred in fetchresultcontroller")
}
tableViewMain.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count = 0
if let sections = fetchedResultsController?.sections {
let currentSection = sections[section]
count = currentSection.numberOfObjects
// in each section we must have only 1 row
if count > 0 {
count = 1
}
}
return count
}
But in this case contacts will be sorted by id, but I need to sort them by date of last message. But if I change sortDescriptors to fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false), NSSortDescriptor(key: "contact.id", ascending: false)] to sort by date first, it will not display contacts properly.
Do anybody have ideas how to implement this?
I'd probably change the model to make life easier and do a little more processing earlier. Basically if add another relationship, 1-to-1, between the 2 entities and each time a new message is added is update that relationship - so you always have a direct pointer to the most recent message.
Once you've done that your FRC and your table load speed are trivial because you just use contacts in the fetch. New messages are also handled correctly because the relationship gets updated so the contact changes and the FRC will see it.
Related
I want to fetch Objects in Ascending order from core data. The problem is, I want the objects as
Abc ,abc Ball, ball bat, Cat, can
But, it sends like,
Abc,Ball,Cat ,abc, ball, cat
Following is my code.
class func CD_FetchAllContacts()->[RecentContact]{
var arrResult:[RecentContact] = []
let managedContext = PM.instance().managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "RecentContact")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "firstName", ascending: true, selector: #selector(NSString.caseInsensitiveCompare))]
do {
let results =
try managedContext?.fetch(fetchRequest)
arrResult = results as! [RecentContact]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
print(arrResult.count)
return arrResult
}
The sort descriptor should be like this, with (_ :) at the end:
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "firstName", ascending: true, selector: #selector(NSString.caseInsensitiveCompare(_:)))]
Edit: Actually even with your version of the sort descriptor it's working on a test project I've done. Nothing seems wrong with the code posted. Are you sorting the returned array in other part of your code?
So i am building this app using CoreData.
The two entities I have are Lists and Items. They have a to many relationship i.e. A List can have multiple items.
For example: List1 has Items: item1, item2
I have written the code for storing the Items in the specific list but i am having a difficult time on figuring out how to fetch and proccess the Items from a specific List.
What I have done so far is as follows
func getItemsOnList(){
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
//fetchRequest to get the List
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "List")
let predicate = NSPredicate(format: "title == %#", listName)
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = predicate
if let fetchResults = try? context.fetch(fetchRequest){
if fetchResults.count > 0 {
for listEntity in fetchResults {
let list = listEntity as! List
print(list.title as Any)
itemsOnList = list.contains!
print(itemsOnList)
print("The list with name:\(list.title)has \(itemsOnList.count) items")
}
}
}
}
This function returns an NSSet which is suppose to contain all the Items in that particular List.
My Data model is :
My questions are:
A. Is the way I coded the getItemsOnList() function correct? Or is there something I am doing wrong.
B. Given that the code is correct and the NSSet I get is correct with all the Items, how would I get each Item in that NSSet in order for me to put it on a TableView.
func getItemsWithFilter(filterQuery:NSPredicate,sortBy:String?,order:Bool) -> Array<Items> {
var fetchedResults:Array<Items> = Array<Items>()
let fetchRequest = NSFetchRequest(entityName: "Items")
fetchRequest.predicate = filterQuery
if sortBy != nil{
let sortDescriptor = NSSortDescriptor(key:sortBy! ,
ascending:order )
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = sortDescriptors
}
//Execute Fetch request you can go with your approach to
do {
fetchedResults = try self.mainContextInstance.executeFetchRequest(fetchRequest) as! [Items]
} catch let fetchError as NSError {
print("retrieveById error: \(fetchError.localizedDescription)")
fetchedResults = Array<Items>()
}catch {
fetchedResults = Array<Items>()
}
return fetchedResults
}
for calling this method you can pass the List item in predicate to as query saying fetch Items in which List.id == XXX
let predicate = NSPredicate(format: "ANY list.name in %#", name)
let myResult = self.getItemsWithFilter(predicate,sortBy:nil,order:false)
Answers:
A) Yes. You are using the graph of objects from a fetch. That is the main functionality of Core Data.
B) To fill a table view you cannot use a set. You need some kind of sorted list of elements. That is, an array. Use -orderedArrayUsingDescriptors: to get the sorted array.
Question:
I have Strings: "A", "C", "D", "1", "И" (Russian), "你" (Chinese). And I have string let alwaysFirstString = "?"
I want alwaysFirstString to be always first when I use general iOS sorting. What should be the alwaysFirstString instead of "?" ?
Why I need that:
In my application I display contacts like in Telegram application:
At first I need to display contacts that have their own contacts in my application (server returns me these contacts)
After that I display other contacts (grouped by the first letter in their surname)
I display contacts in group UITableView. So in my DBContact object in core data I have sectionInfo field. Usually this field = A, B, C, F (The first letter of surname), but for some contacts (I want them to be always first) I need to have other symbol. So this symbol should always be the first when I use regular sortDesctriptors in FetchResultController (better for all languages).
Here is the code:
let entityName = kDBContact
let fetchRequest = NSFetchRequest<DBContact>(entityName: entityName)
// let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: DatabaseManager.sharedInstance.managedObjectContext)
// fetchRequest.entity = entityDescription
fetchRequest.fetchBatchSize = 20
// sort
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "sectionInfo", ascending: true), NSSortDescriptor(key: "nameAndSurname", ascending: true)]
// sometimes we have predicate when user tries to search smth
var predicate: NSPredicate?
var formatForPredicate = ""
// filter not to show fake contacts
formatForPredicate += "(isFakeContact == false)"
var argumentArray = [AnyObject]()
// text filter in search bar
if help_needToShowFilteredContacts() {
if formatForPredicate.length > 0 {
formatForPredicate += " AND "
}
formatForPredicate += "((nameAndSurname CONTAINS[cd] %#) OR (phoneNumbers.numberOnlyDigits CONTAINS[cd] %#))"
argumentArray.append(resultSearchController.searchBar.text! as AnyObject)
argumentArray.append(resultSearchController.searchBar.text! as AnyObject)
}
sectionNameKeyPath = "sectionInfo"
// filter to show only favourites
if segmentedControl.selectedSegmentIndex == segmented_favouriteIndex {
sectionNameKeyPath = nil
if formatForPredicate.length > 0 {
formatForPredicate += " AND "
}
formatForPredicate += "(isFavourite == true)"
}
if formatForPredicate.length > 0 {
predicate = NSPredicate(format: formatForPredicate, argumentArray: argumentArray)
}
fetchRequest.predicate = predicate
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest as! NSFetchRequest<NSFetchRequestResult>, managedObjectContext: DatabaseManager.sharedInstance.managedObjectContext, sectionNameKeyPath: sectionNameKeyPath, cacheName: nil)
So you can see fetchRequest.sortDescriptors = [NSSortDescriptor(key: "sectionInfo", ascending: true), NSSortDescriptor(key: "nameAndSurname", ascending: true)] . So in sectionInfo I have this First characters of the surname, and I want registered contacts (with special character in sectionInfo) to be always the 1st.
I used "" for maximum symbol and " " for minimum. Later in fetched result controller delegate I have:
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = fetchedResultsController?.sections {
let currentSection = sections[section]
let name = currentSection.name
if "" == name {
return "#"
}
else if " " == name {
return "Registered contacts"
}
return name
}
return nil
}
I found last symbol here What character to use to put an item at the end of an alphabetic list?
I try to sort my data in an UITableView by date (day month year) in sections and by time (hh:mm:ss) in rows.
I get my data with coredata.
I have read several discussions but it's not clear and not in swift.
lazy var managedObjectContext : NSManagedObjectContext? = {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedObjectContext = appDelegate.managedObjectContext
return managedObjectContext
}()
func fetchLog() {
let fetchRequest = NSFetchRequest(entityName: "Run")
let sortByTimestamp :NSSortDescriptor = NSSortDescriptor(key: "timestamp", ascending: false)
fetchRequest.sortDescriptors = [sortByTimestamp]
do {
fetchResults = try managedObjectContext!.executeFetchRequest(fetchRequest) as! [Events]
resultsArray = fetchResults.reverse()
} catch let error as NSError {
// failure
print("Fetch failed: \(error.localizedDescription)")
}
}
After that I'm a bit lost when its come to define the number of section and rows in section for UITableView.
Thank you for your help
Jojo
Use NSFetchedResultsController and employ a transient property for the date.
Check out the Apple sample code Date Section Titles. It's in Objective-C, but you should try to understand the concept first before you try it in Swift.
I have found many examples of this, but none seem to work for my situation.
My data model is this.
I have a one to many relationship of Project <->> Entry.
Entry has an attribute of category which is a String.
I want to populate a TableView inside a Project that shows all the entries for that Project organized by the Entry.category as the titleForHeaderInSection.
func allCategoriesFetchRequest() -> NSFetchRequest {
var fetchRequest = NSFetchRequest(entityName: "Project")
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = NSPredicate(format:"(ANY entries == %#)", self.currentProject)
return fetchRequest
}
And loading the data....
func loadInitialData() {
fetchedResultsController = NSFetchedResultsController(fetchRequest: allCategoriesFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: "entries.category", cacheName: nil)
fetchedResultsController?.delegate = self
fetchedResultsController?.performFetch(nil)
}
I get no error, but I get no data either. I am a beginner with iOS and have been struggling with this for a couple of days now.
Any help is greatly appreciated.
Well, there is a lot here.
Your current fetch request says "find any project where its entries are equal to the current project". When what you want to say is "find any entry where its project is equal to the current project".
Lets try to do it using an NSFetchRequest.
var fetchRequest = NSFetchRequest(entityName: "Entry")
let sortDescriptor = NSSortDescriptor.sortDescriptorWithKey("name", ascending: true)
let predicate = NSPredicate(format: "project == %#", self.currentProject)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = predicate
The reason your code doesn't error is because its valid code. It will look for all the projects where an entry is equal to the current project, which will be never, because you won't have any projects as entry objects in your entries field of a project. So that is why you are getting no data.
If you don't want to use a NSFetchedResultsController, and if your model is set such that you have a set of Entry objects in the entries field of your Project object, then you can just do:
let entries = self.currentProject.entries
to get all your Entry objects. This will be a NSSet though, so you have to change it into an array to have it sorted
let sortDescriptor = NSSortDescriptor.sortDescriptorWithKey("name", ascending: true)
let sortedEntries = entries.sortedArrayUsingDescriptors([sortDesriptor])
Wrote all this code without testing, so let me know if there is a problem and I will update.
Excellent. Thank you. Just what I was looking for. This is what I tweaked based on ColdLogic's answer. In case it helps someone else.
var fetchRequest = NSFetchRequest(entityName: "Entry")
let sortDescriptor = NSSortDescriptor(key: "category", ascending: true)
let predicate = NSPredicate(format: "project == %#", self.currentProject)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = predicate