I am using Core Data however haven't done any complex queries and am completely lost - come from SQL background so need help creating a NSPredicate.
My question
I need to retrieve a list of Perspective's (with unique name) which in-directly belong to a specific EntityManagedObject.
Overview of the database
As you can see EntityManagedObject can have many EntityManagedObjects and can have many ObjectiveManagedObject's.
EntityManagedObject therefore has a single EntityManagedObject as a parent, and ObjectiveManagedObjective has a single EntityManagedObject as a parent.
ObjectiveManagedObject has one Perspective. A Perspective can belong to many ObjectiveManagedObjects.
make a method,
-(NSArray *) getEntityManagedObjectsWithParentEntity:(EntityManagedObject *) parentObject;
another method,
-(NSArray *) getObjectiveManagedObjectsWithEntityManagedObjects:(NSArray *) entityManagedObjects;
then ,
-(NSArray *) getPerspectivesWithEntityManagedObject:(EntityManagedObject *) entityObject
{
NSArray *objectiveManagedObjects = [self getObjectiveManagedObjectsWithEntityManagedObjects: [self getEntityManagedObjectsWithParentEntity:entityObject] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"Objectives IN %#", objectiveManagedObjects];
}
Related
I'm running into a problem which should be very basic and have an obvious answer, and I have read through some of Apple's documentation as well as answers here and tutorials online.
I have a tableview displaying a list of "Month" entities (app uses Core Data). It segues to another tableview that I want to show a list of all days contained in that parent month. So, tapping a row on the list of "Month" entities would ideally list only the days in, say, January.
Both the Month and Days tableviews have a separate segue to a regular view controller (not a tableview VC) which adds Month entries and Day entries, respectively.
The data is modeled so Month has a to-many relationship (daysInMonths) with Days as its target, while the inverse is a to-one relationship (parentMonth).
Month attributes are 'month' and 'year'. Days attributes are 'month', 'day', and 'year'.
All the attributes on the model objects are currently strings (not ideal, but I'll change this in the next exercise).
I can add Month and Days entries. I can delete them. The thing is, when I tap on a row in the first tableview (Month), it displays every day entry, not filtered at all.
I'm assuming that when I set up the fetched results controller in my Days tableview .m file, I would use a predicate to filter only the days that match a given parent month.
My fetch request in this tableview is on a Days object. Would I have to instantiate on Month object within that fetch method when setting the predicate?
I don't want to create an additional Model object that shows up in the tableview when all I want to do is set the "parentMonth' relationship to associate the data correctly.
I understand the idea of relationships conceptually, but I'm a bit unclear how to actually set them in code.
What is the best place to set up such a filter?
I realize I'm probably not being clear enough, so I'll answer any other questions as best I can.
I know I'm missing something basic, as this kind of functionality is used everywhere, but reading on my own hasn't made the point hit home yet...
#implementation DaysCDTVC
- (void)setupFetchedResultsController
{
// 1 - Get Entity
NSString *entityName = #"Days";
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
// 3 - Filter
//request.predicate = [NSPredicate predicateWithFormat: ????????????????
// 4 - Sort
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"month"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
//5 - Fetch
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[self performFetch];
}
the "performFetch" method is in an associated class. It's basically the same as what Paul Hegarty taught in the Stanford iTunes lectures.
I transposed code from one of my own projects. This should solve your problem. Let me know if you need clarification or have problems.
In your days table view controller, make a public property:
#property (nonatomic, strong) NSString *month;
in the month table view controller, pass over the selected month to the days table view controller:
- (void)prepareForSegue
{
//Get a pointer to the destination TVC.
//Your final code should have a check to make sure the destination TVC is what you expect
DaysTableViewController *destinationTVC = (DaysTableViewController*)segue.destinationViewController;
//Pass over the month from the selected row. I'm calling it selected month.
//If you need help getting the selected month, let me know
destinationTVC.month = selectedMonth
}
In the days table view controller, build your fetch request using the passed over month
- (void)setupFetchedResultsController
{
//Entity
NSString *entityName = #"Days";
//Fetch Request
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
//Sort
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"day" ascending:YES]];
//Predicate
request.predicate = [NSPredicate predicateWithFormat:#"ANY SELF.month = %#", self.month];
//Fetch using the code you posted earlier
}
I know this is a stupid question, but I've looked for 45 mins now and can't seem to get it right. I am practicing with methods and have the following method in a delegate class called QuotesAppDelegate.
- (NSArray *) getQuoteMaps: fromSubId:(NSString *)subId {
QuotesAppDelegate *appDelegate = (QuotesAppDelegate *)[[UIApplication sharedApplication] delegate];
self.quotes = [appDelegate quotes];
self.quoteMaps = [appDelegate quoteMaps];
//get the quote_ids from quote_map for this subject_id
NSPredicate *filterSubjectId = [NSPredicate predicateWithFormat:#"subject_id == %#", subId];
NSArray *quoteMapSection = [self.quoteMaps filteredArrayUsingPredicate:filterSubjectId];
NSLog(#"appDelegate getQuoteMaps Count: %i", quoteMapSection.count);
return quoteMapSection;
}
I want to call this from SubjectViewController class here:
NSArray *quoteMapSection = [appDelegate.getQuoteMaps fromSubId:selectedSubject.subject_id];
but get an error on the appDelegate.getQuoteMaps part. I tried it several other ways and am not sure what the right syntax is.
Can someone bail me out here?
Change the function signature to
- (NSArray *) getQuoteMapsFromSubId:(NSString *)subId
and then call like this
NSArray *quoteMapSection = [appDelegate getQuoteMapsFromSubId:selectedSubject.subject_id];
I see an error in the very first line:
- (NSArray *) getQuoteMaps: fromSubId:(NSString *)subId
you arent specifying the type nor the name of the first parameter.
- (NSArray *) getQuoteMaps:(NSTypeHere *) yourParamHere fromSubId:(NSString *)subId
or remove the first colon all together:
- (NSArray *) getQuoteMapsfromSubId:(NSString *)subId
Then call it:
[yourObject getQuoteMaps: aVarHere fromSubId: anotherVarHere];
or
[yourObject getQuoteMapsfromSubId: aVarHere];
In general the method call syntax in Objective-C is [object method:firstArgument parameter:secondArgument]. That is an opening brace, the object you want to send the message to, and then a repetition of the methods signature including parameter names but substituting the parameters for their arguments. One might argue that this is very verbose, but it is also very well readable.
So in your specific case, the right syntax is
[appDelegate getQuoteMapsFromSubId:selectedSub]
assuming you fix your declaration to
- (NSArray *) getQuoteMapsFromSubId:(NSString *)subId
Now to the dot-syntax you used. The dot syntax can be used to call parameterless methods with a return type, such as property getters, or to call void methods with a single parameter on the left side of an assignment expression. It should really only be used for properties to avoid confusing people. You can find more info here http://eschatologist.net/blog/?p=160
This line:
NSArray *quoteMapSection = [appDelegate.getQuoteMaps fromSubId:selectedSubject.subject_id];
is very odd.
First of all, you can't have parameters defined that way. You need to take some argument like:
- (NSArray *) getQuoteMaps:(NSObject*)object fromSubId:(NSString *)subId;
Second, calling a method cannot be done by using appDelegate.getQuoteMaps... that is a property reference. You would need to do something along the lines of
[appDelegate getQuoteMaps:nil fromSubId:selectedSubject.subject_id];
Finally, I would just change the header of the method to be:
- (NSArray *) getQuoteMapsFromSubId:(NSString *)subId;
And skip the first argument you didn't specify a purpose for altogether.
You can t invoke a method with classInstance.methodName.
The right format is
NSArray *quoteMapSection = [[appDelegate getQuoteMaps] fromSubId:selectedSubject.subject_id];
I'm searching for a better alternative to deal with this problem.
In a CoreData model I have an NSManagedObject called Project. In its subclass I override the accessor method (setter) for its label attribute. Here I check whether the same label is already used. If it is, I add an underscore and a number to the label, e.g. "MyProject" is renamed to "MyProject_1". Of course I also have to check whether I find the label "MyProject" or "MyProject_"+number. I do that with a Regular Expression.
NSString *regexString = [NSString stringWithFormat:#"%#_[0-9]+", value];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(label = %#) OR (label MATCHES %#)", value, regexString];
[request setPredicate:predicate];
Then I check how many results are fetched, lets say 5, so I know that the next one hast to be called "MyProject_6".
It works fine but you probably have already noticed that there is a little problem with this code: What happens if I have the following labels:
MyProject_1, MyProject_2, MyProject_3
and the user decides to call a project MyProject_55.
Then my search would retrieve 4 elements and the next project would be labeled MyProject_5 instead of MyProject_4. And what it's worse, at some point, I would end up having two MyProject_55. I know it's unlikely to happen, but it can :).
Any ideas for something better?
Here's the accessor method
#pragma mark - Setter for label
- (void)setLabel:(NSString *)aLabel
{
if ([[self primitiveValueForKey:#"label"] isEqualToString:aLabel])
{
return;
}
NSMutableArray *objects = [self fetchObjectsWithValueEqualTo:aLabel];
NSUInteger objectsCount = [objects count];
aLabel = objectsCount > 0 ? [NSString stringWithFormat:#"%#_%d",aLabel, objectsCount] : aLabel;
[self willChangeValueForKey:#"label"];
[self setPrimitiveValue:aLabel forKey:#"label"];
[self didChangeValueForKey:#"label"];
}
Its a little expensive but the simplest way out of this dilemma is once you have a new label decision "MyLabel_4" recheck if that label exists in the store.
Rinse and repeat until you really have a unique label. Core Data is very efficient so this isnt going to matter in a userland case.
I have an NSMutableArray which can hold several objects. I would like to check if an object exists, and if so, alter it. I was wondering about checking it. I thought this would work:
if ([[[self.myLibrary objectAtIndex:1] subObject] objectAtIndex:1]) // do something
However, this will crash if there aren't any subObjects at index 1.
So I guess the problem is that the above does not return nil if there isn't anything at this Index.
Is there another easy way to check or will I have to count through the array etc.? I know there are other posts on stackoverflow on this, but I haven't found a simple answer yet.
Any explanations / suggestions welcome. Thanks!
No check simply using :
[myArray indexOfObject:myObject];
or
[myArray containsObject:myObject];
These methods check every object using isEqual.
For example:
NSUInteger objIdx = [myArray indexOfObject: myObject];
if(objIdx != NSNotFound) {
id myObj = [myArray objectAtIndex: objIdx];
// Do some alter stuff here
}
If this is a pattern you use a lot, you could add a category to NSArray called something like safeObjectAtIndex:, which takes an index, does the bounds checking internally:
-(id)safeObjectAtIndex:(NSUInteger)index {
if (index >= [self count])
return nil;
return [self objectAtIndex:index];
}
Assuming the object you are using to search with and the actual object in the array are the exact same instance, and not two different objects that are equal according to an overridden isEqual: method, you can do this:
if ([array containsObject:objectToSearchFor]) {
// modify objectToSearchFor
}
If the two objects are different instances which are equal according to isEqual:, you will have to use code like this:
NSUInteger index = [array indexOfObject:objectToSearchFor];
if (index != NSNotFound) {
id objectInArray = [array objectAtIndex:index];
// modify objectInArray
}
NSArray (which is the NSMUtableArray superclass) has lots of methods for finding objects. Have a look at the documentation.
You can either rely on the equals method (e.g. indexOfObject:) or provide a block (e.g indexOfObjectPassingTest:) which is pretty funky.
It's fairly common in Objective C to be using the Mutable version of a class but rely on methods in the non mutable superclass so it's always a good idea when checking the online documentation to look at the superclass.
I have a Table with over 3000 entries and searching is very slow.
At the moment I am doing just like in the 'TableSearch' example code (but without scopes)
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText: searchString];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
And the filterContentForSearchText method is as follows:
- (void) filterContentForSearchText:(NSString*)searchText
{
// Update the filtered array based on the search text
// First clear the filtered array.
[filteredListContent removeAllObjects];
// Search the main list whose name matches searchText
// add items that match to the filtered array.
if (fetchedResultsController.fetchedObjects)
{
for (id object in fetchedResultsController.fetchedObjects)
{
NSString* searchTarget = [tableTypeDelegate getStringForSearchFilteringFromObject:object];
if ([searchTarget rangeOfString:searchText
options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)].location != NSNotFound)
{
[filteredListContent addObject:object];
}
}
}
}
My question is twofold:
How do can I make the searching process faster?
How can I stop the search from blocking the main thread? i.e. stop it preventing the user from typing more characters.
For the second part, I tried "performSelector:withObject:afterDelay:" and "cancelPreviousPerformRequests..." without much success. I suspect that I will need to use threading instead, but I do not have much experience with it.
Answer for: "How do can I make the searching process faster?"
It seams that you are using core data results in your table. So it's better let core data do the filtering for you.
So create a new fetchedResultController using a NSPredicate for filtering.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name = %#", searchText];
[fetchRequest setPredicate:predicate];
using MATCHES instead of = let you define a regular expression comparison (for a case insensitive compare)
I ended up doing the searching as a NSOperation, so as not to block the main thread. I also did like Reinhard suggested and used a fetchedResultController.
There is a really good video on NSOperations on the apple developer site. I was called something like Advanced iPhone part 1 if I recall.