I am stumped by this: I've tested the filter function of my app in the iPhone Simulator 4.3 and 5.0 and everything works, but on the iPhone the predicate gets me the wrong results. (I suspect it has something to do with the regex, but I don't see an error.)
if (!selectionDidChange)
return;
[matches release];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"meta LIKE[c] %#",
UniversalKeyword];
NSPredicate *regional = [NSPredicate predicateWithFormat:#"regional == NIL OR regional == NO OR "
#"(regional == YES AND title.%K != NIL)", CurrentLanguage];
NSPredicate *exclusive = (exclusiveUpgrade ? [NSPredicate predicateWithValue:YES] :
[NSPredicate predicateWithFormat:#"exclusive == NIL OR exclusive == NO"]);
NSMutableArray *predicates = [[[NSMutableArray alloc] initWithCapacity:5] autorelease];
for (int i = 0; i < L(keywords); i++)
{
int selection = [D(A(keywords, i), #"selection") intValue];
if (selection >= 0)
A_ADD(predicates, ([NSPredicate predicateWithFormat:#"meta MATCHES[c] %#",
S(S(#".*\\b", D(A(D(A(keywords, i), #"values"), selection), #"name")), #"\\b.*")]));
}
NSPredicate *compound = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
[predicates removeAllObjects];
[predicates addObject:predicate];
[predicates addObject:compound];
predicate = [NSCompoundPredicate andPredicateWithSubpredicates:A_NEW(regional, exclusive,
[NSCompoundPredicate orPredicateWithSubpredicates:predicates])];
matches = [[entries filteredArrayUsingPredicate:predicate] retain];
selectionDidChange = NO;
For clarification:
entries and keywords are arrays of dictionaries, although keywords is a bit more complex. Important is that each dictionary in entries contains a string named meta that can look something like this: "A, B, C, D". And if the user searches for "C", the regex should match. There are other criteria that don't seem to be the problem, since I checked the compiled predicate and it looks fine.
I should mention, the first part of the predicate (meta LIKE[c] %#) gives me the expected result on the iPhone as well.
I have some used convenience macros here: A_ADD = addObject:, D = objectForKey:, A = objectAtIndex:, A_NEW = arrayWithObjects:, L = count and S = stringByAppendingString:. (Yeah, I'm lazy :D )
What am I overlooking here?
Here are the key points in case anybody else has a similar problem:
There are no functional differences between NSPredicate on the iPhone and the corresponding implementation on the iOS Simulator.
If your app behaves differently on the actual device than on the Simulator, double-check file names and other strings for capitalization, like jmstone said.
If the problem persists, remove the app both from the Simulator and from the device. Xcode has many automatic behaviors, but it doesn't clean up anything on the Simulator or the device.
Related
I am using a NSPredicate to search numbers in the list using UISearchBar ,
it works in case of strings but does not work for an integer
I am using the following predicate
predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%# contains[c] %d", #"number", [searchBar.text intValue]]];
[objectArray filterUsingPredicate:predicate];
[tableview reloadData];
FOR example if I type 1 then all the ones in the array must be listed, I have tried == it works only for the exact number if tried any work around for this any body?
Now I get an error if I use this method "Can't use in/contains operator with collection"
I think this predicate should work for you:
predicate = [NSPredicate predicateWithFormat:#"self.number.stringValue CONTAINS %#",searchBar.text];
After thinking about this, I'm not sure why self.number.stringValue works, but it did when I tested it (self.number is an int). Not sure why I can send stringValue to an int?
Predicates can be tricky to work with, so perhaps an alternative would work for you:
NSInteger index = 0;
while (index < objectArray.count)
{
NSString *currentString = [objectArray objectAtIndex:index];
if ([currentString rangeOfString:searchBar.text].length == 0)
{
[objectArray removeObjectAtIndex:index];
continue;
}
index++;
}
Here, any strings in your array that do not contain your searchBar text will be removed.
I have the above CoreData Model in my first iPad app. I'm building a filtering system in a TableViewController as shown below. The problem is that whenever I make a UI change, toggle a switch of tap a button, My UI becomes non-responsive for a second or two. I run a really long function that recreates the fetch request for the photos, then runs more count fetches to determine whether the control should be enabled. I just don't know how I can break this apart in a meaningful way that would prevent the hang. Even If I need to add a spinning view for a second or so, I'm happy with that. Just want to get rid of the lag.
As I mentioned, this is my first attempt at iOS development so I would appreciate any suggestions...
-(void) refilterPhotos {
/*
* First section builds the NSCompoundPredicate to use for searching my CoreData Photo objects.
Second section runs queries so 0 result controls can be disabled.
*/
subpredicates = [[NSMutableArray alloc] init];
NSPredicate *isNewPredicate;
if(newSwitch.on) {
isNewPredicate = [NSPredicate predicateWithFormat:#"is_new == 1"];
} else {
isNewPredicate = [NSPredicate predicateWithFormat:#"is_new == 0"];
}
[subpredicates addObject:isNewPredicate];
//Photo Types
PhotoType *photoType;
NSPredicate *photoTypePredicate;
for (UISwitch *photoSwitch in photoSwitches) {
PhotoType * type = (PhotoType *) photoSwitch.property;
if([type.selected boolValue] == YES) {
NSLog(#"photo_type.label == %#", type.label);
photoType = type;
photoTypePredicate = [NSPredicate predicateWithFormat:#"photo_type.label == %#", type.label];
break;
}
}
//Feed Types
FeedType *feedType;
NSPredicate *feedTypePredicate;
for (UISwitch *feedSwitch in feedSwitches) {
FeedType * type = (FeedType *) feedSwitch.property;
if([type.selected boolValue] == YES) {
NSLog(#"feed_type.label == %#", type.label);
feedType = type;
feedTypePredicate = [NSPredicate predicateWithFormat:#"feed_type.label == %#", type.label];
break;
}
}
//Markets
NSArray *filteredMarkets = [model.availableMarkets filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"selected == 1"]];
for (Market *market in filteredMarkets) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY markets.name == %#", market.name]];
}
//Tags
NSArray *filteredTags = [model.availableTags filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"selected == 1"]];
for (Tag *tag in filteredTags) {
NSLog(#"ANY tags.name == %#",tag.name);
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY tags.name == %#", tag.name]];
}
if(photoTypePredicate)
[subpredicates addObject:photoTypePredicate];
if(feedTypePredicate)
[subpredicates addObject:feedTypePredicate];
NSPredicate *finished = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];//Your final predicate
model.availablePhotos = [model fetchPhotoswithPredicate:finished];
[[self parentViewController] setTitle:[NSString stringWithFormat:#"%d items",[model.availablePhotos count]]];
NSLog(#"FILTERED PHOTOS:::: %d", [model.availablePhotos count]);
[gridVC reloadGrid];
/**
* Filtering Section Here, I'm running count requests for each grouping of controls to ensure if they're selected, results will be returned.
* If zero results, I'll disable that control. For the switch-based controls, I need to removed them before running my fetches since there can only be
* one switch value per photo.
*/
//Have to remove the existing type predicate since they're exlcusive values
[subpredicates removeObject:isNewPredicate];
//New Toggle
NSPredicate *newRemainderPredicate = [NSPredicate predicateWithFormat:#"is_new == %d",newSwitch.on?0:1];
[subpredicates addObject:newRemainderPredicate];
if([model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]]<1) {
[newSwitch setEnabled:NO];
} else {
[newSwitch setEnabled:YES];
}
[subpredicates removeObject:newRemainderPredicate];
[subpredicates addObject:isNewPredicate];
[subpredicates removeObject:photoTypePredicate];
//Photo Type Toggles
NSArray *remainderPhotoTypes = [photoSwitches filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"on == NO"]];
for( UISwitch*control in remainderPhotoTypes) {
PhotoType *remainderPhotoType = (PhotoType*)control.property;
[subpredicates addObject:[NSPredicate predicateWithFormat:#"photo_type == %#", remainderPhotoType]];
if([model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]]<1) {
//NSLog(#"PHOTOTYPE OFF %#", remainderPhotoType.label);
control.enabled = NO;
} else {
//NSLog(#"PHOTOTYPE ON %# count = %d", remainderPhotoType.label, [[model fetchPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]] count]);
control.enabled = YES;
}
remainderPhotoType.enabled = [NSNumber numberWithBool:control.enabled];
[subpredicates removeObject:[NSPredicate predicateWithFormat:#"photo_type == %#", remainderPhotoType]];
}
if(photoTypePredicate)
[subpredicates addObject:photoTypePredicate];
[subpredicates removeObject:feedTypePredicate];
//Feed Type Toggles
NSArray *remainderFeedTypes = [feedSwitches filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"on == NO"]];
for( UISwitch*control in remainderFeedTypes) {
PhotoType *remainderFeedType = (PhotoType*)control.property;
[subpredicates addObject:[NSPredicate predicateWithFormat:#"feed_type == %#", remainderFeedType]];
if([model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]]<1) {
control.enabled = NO;
} else {
control.enabled = YES;
}
remainderFeedType.enabled = [NSNumber numberWithBool:control.enabled];
[subpredicates removeObject:[NSPredicate predicateWithFormat:#"feed_type == %#", remainderFeedType]];
}
if(feedTypePredicate)
[subpredicates addObject:feedTypePredicate];
NSArray *remainderMarkets = [[model availableMarkets] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"selected == 0"]];
//Markets..many-to-many so I don't remove the existing predicate
for( Market *remainderMarket in remainderMarkets) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY markets == %#", remainderMarket]];
NSInteger countForTag = [model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]];
if(countForTag<1) {
remainderMarket.enabled = [NSNumber numberWithInt:0];
} else {
remainderMarket.enabled = [NSNumber numberWithInt:1];
}
[subpredicates removeObject:[NSPredicate predicateWithFormat:#"ANY markets == %#", remainderMarket]];
}
NSArray *remainderTags = [[model availableTags] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"selected == 0"]];
//TAGS..many-to-many so I don't remove the existing predicate
int tagCounter = 0;
for( Tag *remainderTag in remainderTags) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY tags == %#", remainderTag]];
NSInteger countForTag = [model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]];
if(countForTag<1) {
NSLog(#"TAG OFF %#", remainderTag.name);
remainderTag.enabled = 0;
} else {
NSLog(#"TAG ON %# count = %d", remainderTag.name, countForTag);
remainderTag.enabled = [NSNumber numberWithInt:1];
}
[subpredicates removeObject:[NSPredicate predicateWithFormat:#"ANY tags.name == %#", remainderTag.name]];
tagCounter ++;
}
//Update the controls with this new data
[self.tableView reloadData];
}
OK, there are several things to consider here.
First, I would consider creating indexes for the main search fields. Without an index, each search is linear, because it has to check the value of each record. An index will result in much faster searching times.
Second, I'd be very careful about ordering in a compound predicate. It will filter them based on order. Thus, you want to make you fastest, most filtering predicates first. Trim the possible solution space a quickly as possible.
You can gain a lot by indexing the attributes you use in the first 1-3 predicates. I note at the bottom, when you query for counts, you are still using the same compound predicate. Do you really want that? Also, in this code
//Have to remove the existing type predicate since they're exlcusive values
[subpredicates removeObject:isNewPredicate];
//New Toggle
NSPredicate *newRemainderPredicate = [NSPredicate predicateWithFormat:#"is_new == %d",newSwitch.on?0:1];
[subpredicates addObject:newRemainderPredicate];
You are removing the is_new check from the front, and placing it at the rear. If you are just checking this one predicate to toggle that switch, and you only care to see if there are 0 or more, why even use the entire compound predicate? Is the "toggle" going to be on/off relative to all the other fields?
If you continue with this, remember, it is going to do all those other predicates first (and some are references). Try to keep them in a good order to filter as much as possible, as quickly as you can.
Third, using references is convenient, but expensive. You can possible get better performance by querying those separately, and then using the compound predicate to filter the in-memory objects.
Fourth, you should be executing all these queries in a separate thread. That is very easy to do, but the exact method depends on your current ManagedObjecttContext arrangement. Do you have a single MOC, a parent/child relationship, a UIManagedDocument? Basically, you can create a separate MOC, and call performBlock to execute the fetches. In fact, you can fire all those fetches off asynchronously at the same time with multiple MOCs.
Then, you can just call into the main thread when they are done.
Finally, you may want to consider denormalizing your database. It will cause you to use more space, but fetches will be much faster. Specifically, the relationship fields... you could put the photo/feed labels in with the Photo itself. That way, when searching, you don't have to do the extra join to get those records.
So, it's not a simple answer, but implement each of these, and see if your performance does not improve considerably (not to mention your UI responsiveness).
Using Core Data w/a sqlite store on iPhone.... I've got a bunch of comic book image entities, each with a string that includes the comic's issue#, e.g.: image.imageTitle = #"Issue 12: Special Edition";
Part of the UI allows the user to type in an issue number to jump to the next issue. My initial code for this was sloooooooow because imageAtIndex: queries Core Data for one object at a time. Over several hundred issues, it could take upwards of 40 seconds just to get through the first loop!
Slow Code:
// Seek forward from the next page to the right
for (i = currentPage + 1; i < [self numberOfPages]; i++) {
iterationString = [[self imageAtIndex:i] imageTitle];
iterationNumber = [[iterationString stringByTrimmingCharactersInSet:nonDigits] intValue];
if (issueNumber == iterationNumber) {
keepLooking = NO;
break;
}
}
// If nothing was found to the right, seek forward from 0 to the current page
if (i == [self numberOfPages] && keepLooking) {
for (i = 0 ; i < currentPage; i++) {
iterationString = [[self imageAtIndex:i] imageTitle];
iterationNumber = [[iterationString stringByTrimmingCharactersInSet:nonDigits] intValue];
if (issueNumber == iterationNumber) {
keepLooking = NO;
break;
}
}
}
Hoping for a much more efficient solution, I decided to try making a direct query on Core Data like so:
NSString *issueNumber = #"12";
NSString *issueWithWordBoundaries = [NSString stringWithFormat:#"\\b%#\\b",issueNumber];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(groupID == %#) AND (imageTitle CONTAINS %#)", groupID, issueWithWordBoundaries];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CBImage" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setIncludesSubentities:NO]; // Not sure if this is needed, but just in case....
// Execute the fetch
NSError *error = nil;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
// [fetchedImages count] == 0
Between the Predicate Programming Guide and the ICU regex specs, I figured the \b's would help prevent a search for 12 returning 120, 121, 122, etc. Instead, it doesn't return anything from the store at all!
On the other hand, if I leave off the word boundaries and search instead for stringWithFormat:#"%#",issueNumber, I get dozens of managed objects returned, from 12 to 129 to 412.
My best guess at this point is that I've run into one of Core Data's Constraints and Limitations. If not, what am I doing wrong? If so, is there a workaround that offers both an exact match and the speed of a single fetch?
\b refers to a backspace character. Try
[NSString stringWithFormat:#"\\b%#\\b",issueNumber];
// ^^ ^^
And to perform RegEx match, use MATCHES, not CONTAINS.
Turns out the word boundaries were a red herring: My problem was a variant on this issue: The regex couldn't succeed without some wild cards to match the bits I didn't care about in the imageTitles.
The general case solution to finding an exact phrase using NSPredicate is therefore:
NSString *exactPhrase = #"phrase_you_hope_to_find_in_another_string";
NSString *regularExpression = [NSString stringWithFormat:#".*\\b%#\\b.*",exactPhrase];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"stringToSearch CONTAINS %#", exactPhrase];
// Assuming a string or an entity's string attribute named stringToSearch
I have a NSMutableArray which contains a few NSString objects. How can I test if the array contains a particular string literal?
I tried [array containsObject:#"teststring"] but that doesn't work.
What you're doing should work fine. For example
NSArray *a = [NSArray arrayWithObjects:#"Foo", #"Bar", #"Baz", nil];
NSLog(#"At index %i", [a indexOfObject:#"Bar"]);
Correctly logs "At index 1" for me. Two possible foibles:
indexOfObject sends isEqual messages to do the comparison - you've not replaced this method in a category?
Make sure you're testing against NSNotFound for failure to locate, and not (say) 0.
[array indexOfObject:object] != NSNotFound
Comparing against string literals only works in code examples. In the real world you often need to compare against NSString* instances in e.g. an array, in which case containsObject fails because it compares against the object, not the value.
You could add a category to your implementation which extends NS(Mutable)Array with a method to check wether it contains the string (or whatever other type you need to compare against);
#implementation NSMutableArray (ContainsString)
-(BOOL) containsString:(NSString*)string
{
for (NSString* str in self) {
if ([str isEqualToString:string])
return YES;
}
return NO;
}
#end
You may also use a predicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF IN %#", theArray];
BOOL result = [predicate evaluateWithObject:theString];
for every object
[(NSString *) [array objectAtIndex:i] isEqualToString:#"teststring"];
Hi how can I validate email address, username, fullname and date of birth for my registration form inside an iphone application.
You can use NSPredicate with regular expressions in iPhone OS > 3.0 like so
- (BOOL) validateEmail: (NSString *) candidate {
NSString *emailRegex = #"[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", emailRegex];
return [emailTest evaluateWithObject:candidate];
}
Another simple way of validating an email address is using the US2FormValidator framework.
Example:
US2ValidatorEmail *emailValidator = [US2ValidatorEmail alloc] init];
US2ConditionCollection *collection1 = [emailValidator checkConditions:#"example#example.com"];
// collection1 == nil, thus YES
US2ConditionCollection *collection2 = [emailValidator checkConditions:#"example#example."];
// collection2.length > 0, thus NO
US2ConditionCollection *collection3 = [emailValidator checkConditions:#"example"];
// collection3.length > 0, thus NO
BOOL isValid = [emailValidator checkConditions:#"example#example.com"] == nil;
// isValid == YES
You can simply use the US2ValidatorTextField instead of UITextField and connect to this US2ValidatorEmail. The text field will tell you what went wrong and if the user corrected the text.
The framework can be found on GitHub or Softpedia.
If you would like to only check phone numbers iOS also provides so called NSDataDetector's.
Usage like:
theTextView.dataDetectorTypes = UIDataDetectorTypePhoneNumber;
Read more about it here: http://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSDataDetector_Class/Reference/Reference.html