Minimum letter for string sorting - ios

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?

Related

core data taking time to check whether data is present or not against particular id in for loop of 1000

I have 2 entities: Components and SurveyWebReadings. In SurveyWebReadings we have readings against component id. In Component entity we have all components with their id, area, subarea, facilityid attributes.
Now we want to check whether for particular Component id is there readings available or not in SurveyWebReading table. For that we get componentIds array from Component entity using predicates for area & subarea & facilityId. We get the maximum componentId array of count 1200. When we check whether reading is available or not against that ids in for loop of 1200 ids, application get freezes.
Any other way of checking whether reading present or not against that component id except realationship?
Below are the functions for fetching component ids & checking whether reading present or not against that component id & the code we called it in for loop:
func fetchComponentArrayByAreaSubarea(facilityID: Int32, area: String, subArea: String) -> [Components] {
let fetchRequest : NSFetchRequest<Components> = Components.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "faclityID == %ld AND area == %# AND subarea == %#", facilityID, area, subArea)
//let sortDescriptor = NSSortDescriptor(key: "componentId", ascending: true)
//fetchRequest.sortDescriptors = [sortDescriptor]
let sort = NSSortDescriptor(key: "iD", ascending: true)
fetchRequest.sortDescriptors = [sort]
do {
let searchResults = try managedContext.fetch(fetchRequest)
return searchResults
} catch {
}
return []
}
func checkIfCompExistInReadingTable(surveyId: Int32, compId: Int32) -> Bool {
let fetchRequest = NSFetchRequest<SurveyReadingWeb>(entityName: "SurveyReadingWeb")
fetchRequest.predicate = NSPredicate(format: "surveyId == %ld AND componentId == %ld AND readyToSync == 1", surveyId, compId)
fetchRequest.fetchLimit = 1
var results: [SurveyReadingWeb] = []
do {
results = try managedContext.fetch(fetchRequest)
}
catch {
}
return results.count > 0
}
self.arrCompValues = DatabaseManager.sharedManager.fetchComponentArrayByAreaSubarea(facilityID: self.currentSurvey?.facilityId ?? 0, area: self.lblDisplaySelectedArea.text ?? "", subArea: self.lblDisplaySelectedSubarea.text ?? "")
for component in arrCompValues {
let comp = component as? Components
let ifCompExist: Bool = DatabaseManager.sharedManager.checkIfCompExistInReadingTable(surveyId: currentSurvey?.surveyId ?? 0, compId: comp?.componentId ?? 0)
if ifCompExist {
print("is reading ava:- \(ifCompExist), ")
arrCompReadingDone1.append(comp!)
}
}

Searching with NSFetchResult Controller

I've been looking on how to search using a NSFetchResultController but most post I came across are from 2 years ago. Okay I'm trying to filter the objects, which in my case are Pokemon. Here is my function I'm using.
func attemptPokemonFetch(generation: String = "1", name: String = "") {
let context = coreData.persistentContainer.viewContext
let request: NSFetchRequest<Pokemon> = Pokemon.fetchRequest()
let sortByName = NSSortDescriptor(key: "id", ascending: true)
request.sortDescriptors = [sortByName]
request.predicate = NSPredicate(format: "generation = %#", generation)
if !name.isEmpty {
request.predicate = NSPredicate(format: "name CONTAINS[cd] %#", name)
}
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
do {
try controller.performFetch()
}
catch let err {
print(err)
}
}
I call this function in view did load, search bar did change text and when the segment changes index value. I know how to filter the Pokemon and use the searching. However the problem I come across is when I begin searching. When searching for a Pokemon in a specific generation other Pokemon from another generation will also appear. So I'll be at index 2 which is for generation 2 Pokemon and when searching, other Pokemon from different generation will be appearing. I'm not sure if its how I initialize the context. This is how my search bar function look like.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar == pokemonSearchBar {
let text = pokemonSearchBar.text!
self.attemptPokemonFetch(name: text)
collectionView.reloadData()
}
}
Another Example: Lets say I have 3 types of people some are skinny, normal and fat. When I'm at the index for skinny people I only want to search for people that are skinny not all the people. So what would I need to do to achieve this type of behavior.
Would really appreciate any help. :)
In your code
request.predicate = NSPredicate(format: "generation = %#", generation)
if !name.isEmpty {
request.predicate = NSPredicate(format: "name CONTAINS[cd] %#", name)
}
the second assignment replaces the previously assigned predicate.
What you probably want is
if name.isEmpty {
request.predicate = NSPredicate(format: "generation = %#", generation)
} else {
request.predicate = NSPredicate(format: "generation = %# AND name CONTAINS[cd] %#", generation, name)
}
A more flexible approach is to use NSCompoundPredicate:
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "generation = %#", generation))
if !name.isEmpty {
predicates.append(NSPredicate(format: "name CONTAINS[cd] %#", name))
}
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)

Querying Core data with predicates

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.

NSFetchedResultsController with table like iMessage

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.

Custom sections with NSSortDescriptor & NSFetchedResultsController in CoreData driven app w iCloud

I'm having problems implementing custom section in App using Core Data with iCloud sync.
I've made a sample App to illustrate my problem : it has a list of events within CoreData (fetching using FRC)
Event enitity :
#objc(Event)
class Event: NSManagedObject {
#NSManaged var timeStamp: NSDate?
#NSManaged var name: String?
#NSManaged var sectionIdentifier :Int32
}
I have implemented custom sections based on timeStamp of item :
In Past
Today
Tomorrow
Next 7 Days
Future
No Date
enum SectionType:Int32{
case inPast = 9
case Today = 10
case Tomorrow
case Next7Days
case InFuture
case NotSet = 14
func title()->String{
switch self {
case .inPast:
return "In Past"
case .Today:
return "Today"
case .Tomorrow:
return "Tomorrow"
case .Next7Days:
return "Next 7 Days"
case .InFuture:
return "In Future"
default:
return "No due date"
}
}
}
Code for FRC
private var _fetchedResultsController: NSFetchedResultsController? = nil
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
fetchRequest.fetchBatchSize = 20
let sortDescriptors = [
NSSortDescriptor(key: "sectionIdentifier", ascending: true),
NSSortDescriptor(key: "timeStamp", ascending: true)
]
fetchRequest.sortDescriptors = sortDescriptors
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: self.managedObjectContext!,
sectionNameKeyPath: "sectionIdentifier",
cacheName:nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
abort()
}
return _fetchedResultsController!
}
Everything seems to be working, Events being grouped by sectionIdentifier.
But if it's now synced with device in other timezone, the events will be grouped incorrectly because of time diff.
Using transient property would help, but then I cannot use NSSortDescriptor to sort sections.
Are there any solutions ? I really don't want to populate arrays per section and so on.
Kind Regards
So I've solved my problem by implementing a transient property.
The only problem left Events without date are placed on top of list.
But I've solved it by adding another property hasDate (Bool) and adding second NSSortDescriptor
ManagedObject :
import Foundation
import CoreData
enum SectionType:String{
case inPast = "10"
case Today = "11"
case Tomorrow = "12"
case Next7Days = "13"
case InFuture = "14"
case NotSet = "15"
func title()->String{
switch self {
case .inPast:
return "In Past"
case .Today:
return "Today"
case .Tomorrow:
return "Tomorrow"
case .Next7Days:
return "Next 7 Days"
case .InFuture:
return "In Future"
default:
return "No due date"
}
}
}
#objc(Event)
class Event: NSManagedObject {
#NSManaged var timeStamp: NSDate?
#NSManaged var noDate: Bool
#NSManaged var name: String?
var sectionIdentifier :String? {
get {
var str : String
if let aDate = self.timeStamp {
if aDate.isToday() || aDate.isYesterday() {
str = SectionType.Today.rawValue
} else if aDate.isTommorow() {
str = SectionType.Tomorrow.rawValue
} else if aDate.isNext7Days() {
str = SectionType.Next7Days.rawValue
} else if aDate.inPast(){
str = SectionType.inPast.rawValue
} else {
str = SectionType.InFuture.rawValue
}
}else {
str = SectionType.NotSet.rawValue
}
return str
}
set {
self.sectionIdentifier = newValue
}
}
func setTime(date : NSDate?){
self.willChangeValueForKey("timeStamp")
self.setValue(date, forKey: "timeStamp")
self.didChangeValueForKey("timeStamp")
if let date = date {
self.noDate = false
}
}
class func keyPathsForValuesAffectingSectionIdentifier() -> NSSet {
return NSSet(object: "timeStamp")
}
}
FRC :
private var _fetchedResultsController: NSFetchedResultsController? = nil
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
fetchRequest.fetchBatchSize = 20
let sortDescriptors = [
// NSSortDescriptor(key: "sectionIdentifier", ascending: true),
NSSortDescriptor(key: "noDate", ascending: true),
NSSortDescriptor(key: "timeStamp", ascending: true)
]
fetchRequest.sortDescriptors = sortDescriptors
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: self.managedObjectContext!,
sectionNameKeyPath: "sectionIdentifier",
cacheName:nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
abort()
}
return _fetchedResultsController!
}

Resources