Core Data Predicate - Table View Display Format

Hello, again!


I posted here a couple of days ago, regarding core data and how to properly store it. I did in fact fix this, but I have now faced another small problem of mine. You see, I have a entity called "Profiles". Here I have the following attributes "name", "age", "country". However, what I want to do - is to only display one instance of the names. Let's say I have three profiles named "Bob", and two profiles named "John". Instead of displaying three "Bob" and two "John", how can I avoid this and only display unique names once?


Here is what I have so far, it's not that much - as I don't have any clue on how to do this. I searched on Google, but I didn't find anything.


        let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
        let context = delegate.managedObjectContext
     
        let request = NSFetchRequest(entityName: "Profiles")
        let predicate = NSPredicate(format: "name == '\(selectedName)'")
      
        request.predicate = predicate
     
        var err:NSError?
     
        do {
            ProfileName = try context.executeFetchRequest(request) as! [NSManagedObject]
            ProfileAge = try context.executeFetchRequest(request) as! [NSManagedObject]
            ProfileCountry = try context.executeFetchRequest(request) as! [NSManagedObject]
         
        } catch let err1 as NSError {
            err = err1
        }
     
        if err != nil {
            print("Problem loading data..")
        }


As you may understand, this is only a snippet of my code. Help would be very appreciated.

Searching with "distinct core data", you may find better descriptions...


To use Core Data functionality, you need to write something like this:

        let entityDescription = NSEntityDescription.entityForName("Profiles", inManagedObjectContext: context)!
        let request = NSFetchRequest()
        request.entity = entityDescription
        request.propertiesToFetch = [entityDescription.propertiesByName["name"]!]
        request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        request.returnsDistinctResults = true //<==
        request.resultType = .DictionaryResultType
        //Add predicate if needed.

        do {
            let nameDicts = try context.executeFetchRequest(request) as! [[String: AnyObject]]
            let names = nameDicts.map{$0["name"]!} as! [NSString] as! [String]
            print(names)
        } catch let error as NSError {
            print("Problem loading data: \(error)")
        }


To make it programmatically yourself, using Set is a standard way:

        let request = NSFetchRequest(entityName: "Profiles")
        //Add predicate if needed.
  
        do {
            //Retrieve all Profiles
            let profiles = try context.executeFetchRequest(request) as! [Profiles]
            //Create Array of names for profiles
            let namesArray = profiles.map{$0.name!}
            //Create Set of names to eliminate duplicates
            let namesSet = Set(namesArray)
            //You may want your names sorted
            let names = namesSet.sort()
            print(names)
        } catch let error as NSError {
            print("Problem loading data: \(error)")
        }

Thanks a lot for this source code, this did indeed lead me in the right direction. However, I do get an error when I want to display it in my table view. This is because in your code, and from what it looks like - it returns a dictionary. Here is my code for the table view:



// variables
    var Names : [NSManagedObject] = []
    var Countries : [NSManagedObject] = []
    var Ages : [NSManagedObject] = []


    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let Name = Names[indexPath.row]
            let Country= Countries[indexPath.row]
            let Age = Ages[indexPath.row]
          
            let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TableViewCell
          
          
            if Name.valueForKey("name") != nil {
                cell.nameLabel!.text = ToMeName.valueForKey("name") as! String
            } else {
                cell.nameLabel!.text = ""
            }
            if Country.valueForKey("country") != nil {
                cell.countryLabel!.text = Country.valueForKey("country") as! String
            } else {
                cell.countryLabel!.text = ""
            }
            if Age.valueForKey("age") != nil {
                cell.ageLabel!.text = Age.valueForKey("amount") as! String
            } else {
                cell.ageLabel!.text = ""
            }
          
          
            return cell
}


how would I do this, when I retrieve my data this way? Thanks in advance, and excuse my lack of knowledge, but I really find this Core Data rather advanced.

I do fetch the data in my viewDidLoad - with the code in the first post.

I will just ask for your help one last time, as I am so close to achieve what I have been trying to do for a week now. So, I tried the code you gave me, that looked like this:


    let request = NSFetchRequest(entityName: "Profiles")
        //Add predicate if needed.

        do {
            //Retrieve all Profiles
            let profiles = try context.executeFetchRequest(request) as! [Profiles]
            //Create Array of names for profiles
            let namesArray = profiles.map{$0.name!}
            //Create Set of names to eliminate duplicates
            let namesSet = Set(namesArray)
            //You may want your names sorted
            let names = namesSet.sort()
            print(names)
        } catch let error as NSError {
            print("Problem loading data: \(error)")
        }


However, in my code - I have already defined my variables as NSManagedObjects, that I am fetching the data to - in order to display it in to my table view. However, when I tried to "twist" your code - I got the following error: Value of type 'NSManagedObject' has no member 'name'. This is the code that I used:


     let Names = try context.executeFetchRequest(request) as! [NSManagedObject]
            /
            let namesArray = Names.map{$0.name!} // Here is where the error occurs
            /
            let namesSet = Set(namesArray)
            /
            let names = namesSet.sort()

How exactly would I be able to store this data properly, without any errors? And by that I mean accessing the ".name" property, without having to link to any class of mine (Persons). Because I am fetching from two entities, and I need this functionality that I have on my previous data, that displays properly in my tableview.


Thanks in advance.

I have already defined my variables as NSManagedObjects

Why don't you use Profiles (or Persons?) class?.

If you have some reasons you need to you NSManagedObject directly, I cannot be help of you. I have never used NSManagedObject directly in my Core Data apps.

If I want to share some parts of my code for two NSManagedObject types, I would use protocols.

With defining these:

@objc protocol ProfileType {
    var name: String? {get}
    var age: NSNumber? {get}
    var country: String? {get}
}
extension Profiles: ProfileType {}
extension Persons: ProfileType {}

You can write someting like this:

            let profiles = try context.executeFetchRequest(request) as! [ProfileType]

(I assume you already have Profiles or Persons in your project.)


And I want to ask you one thing. I assumed you only show `name` when you unified your data with `name`. So, the result just needs to be [String].

If not, please describe what you want to display for other properties for inconsistent data:

{name: "John", age: 17, country: "Japan"}

{name: "John", age: 34, country: "US"}

{name: "John", age: 55, country: "Brasil"}

Basically I want to display names (only unique ones), and the age of the person, and the Country. The age and country that the name has, obviously. So when I click the name in the table View, I want to display all records of 'Bob', for instance.


Let's say I have 3 named Bob, 1 named John, and 4 named Alan.


Bob - USA - 20

John - UK - 25

Alan - USA - 19


As you see, only show the result of unique names. However, the country that follows with the age, I want it to display the latest records, and when I click the row I want to display all 'Bob' with all the different ages and Countries.


I hope you understand, as I am not the best to explain my problem. If so, I can try to be more specific and send some pictures.

the country that follows with the age, I want it to display the latest records

You see you need to specify that sort of behaviors. With just saying `only display unique names once`, not many would understand what you intend.


I would write some code myself to fulfill such requirement. Assume we have an Array of ProfileType in `profiles`:

("Bob", "USA", 20),

("Bob", "Japan", 35),

("Bob", "China", 55),

("John", "UK", 25),

("Alan", "USA", 19),

("Alan", "Japan", 22),

("Alan", "China", 35),

("Alan", "Vietnam", 58),


Using Dictionary is another way to eliminate duplicates, which can hold non-duplicate keys an related something.

//Fill up the dictionary, the latter replaces the former.
var profileByName: [String: ProfileType] = [:]
for profile in profiles {
    guard let name = profile.name else {continue}
    profileByName[name] = profile
}
//sort the dictionary and retrieve only values from the result of `sort`.
let represetativeByName = profileByName.sort{$0.0 < $1.0}.map{$1}
print(represetativeByName)

[Alan - Vietnam - 58, Bob - China - 55, John - UK - 25]

I'm very sorry for taking up your time, but unfortunately this piece of code doesn't seem to work. Thanks for trying to help, though.

The code in the previous post wasn't presented for copy/pasting, it was presented as an example of the idea.


The thing is, if this were a classroom assignment, the purpose of the assignment would be:

  • Use sortDescriptors on an NSFetchRequest to group the fetch results by name.
  • Instead of displaying the results of the fetch request directly, display one section and for the rows display information from the groups of the fetch request.

Or, as a variation to the second bullet point, instead of displaying the individual rows, initially display a single row displaying summary information for the group which is replaced by the individual rows when expanded.


But all of that still boils down to:

Step 1: Sort your fetch request by name.

Step 2: Customize your table view code in a manner that goes beyond the scope of a single forum post.


As a good start, the table view and collection view sessions from each year's WWDC videos explain how to do this sort of thing, things like dynamically adding and removing rows to display more information.

Core Data Predicate - Table View Display Format
 
 
Q