How would I detect if an array displays the following in an if statement? (I tried NULL and it didn't work)...
When I NSLog the description of the array, this is what returns:
NSLog(#"%#", [manager purDesc]description]);
2011-08-30 13:43:20.227 PROJECT[2921:f503] manager purDesc Dump:(
{
amt = "\n ";
desc = "\n ";
}
)
I need to say "If [manager purDesc] looks like that, display a UIAlertView".
Sorry everyone, I feel like I'm having trouble getting my point across this morning. If you don't understand, please comment with your question and I'll try to explain better.
Actually, your structure seems to be a dictionary inside an array. Not sure how that all stacks up. To see if all items in a dictionary are only whitespace.
BOOL empty = YES;
NSCharacterSet* wp = [NSCharacterSet whitespaceAndNewlineCharacterSet];
for(NSString* key in dict)
{
NSString* val = [dict objectForKey: key];
// trim white space and check length
if([[val stringByTrimmingCharactersInSet: wp]length])
{
empty = NO;
break;
}
}
the array version is left as an exercise to the reader. :-)
If those fields are NSString, you might want to check out stringByTrimmingCharactersInSet:.
NSString* trimmedAmount = [amt stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
You can remove whitespace and returns by using the NSString method stringByTrimmingCharactersInSet: along with the static set whitespaceAndNewlineCharacterSet.
However, based on the output of [purDesc description], it looks like you may have an NSDictionary, not an array.
Why don't you check the actual elements of the array? somehting like
[manager objecAtIndex:0] = nil
Related
I'm working on a project and I want to be able to handle some template type messages. The template would contain something like:
"{{user1}} has just created an account"
I then have a data map that would give you a location within the NSMutableDictionary where the data is located:
"activity.message.status"
I then want to be able to query the NSMutableDictionary by splitting up that string, so that it becomes something like:
[[[myDictionary objectForKey:#"activity"] objectForKey:#"message"] objectForKey:#"status"]
I could make something as long as it was consistant on being just 3 strings, but some may be more or less.
Any help would be extremely appreciated.
It's actually much easier than splitting strings into keys. Apples Key-Value-Coding allows exactly what you want.
[myDictionary valueForKeyPath:#"activity.message.status"];
A key path is a string of dot separated keys that is used to specify a sequence of object properties to traverse. The property of the first key in the sequence is relative to the receiver, and each subsequent key is evaluated relative to the value of the previous property.
For example, the key path address.street would get the value of the address property from the receiving object, and then determine the street property relative to the address object.
Key-Value Coding Programming Guide
You would do something like,
NSArray *array = [#"activity.message.status" componentsSeperatedByString:#"."];
Which will create an array containing {activity,message,status).
Now you have your array you can use for querying your dictionary.
[[[myDictionary objectForKey:[array objectAtIndex:0]] objectForKey:[array objectAtIndex:1]] objectForKey:[array objectAtIndex:2]];
Which is equivalent to:
[[[myDictionary objectForKey:#"activity"] objectForKey:#"message"] objectForKey:#"status"];
Hope this helps !
It's not clear to me from your question how we should map user1 to activity.message.status. For now I'll assume you mean that the template might contain a string like "{{activity.message.status}}" and you want to be able to parse that.
Here's one iteration that operates on an NSMutableString that can be looped until no match is found:
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\\{\\{.+?\\}\\}"
options:NSRegularExpressionCaseInsensitive
error:&error];
NSRange matchRange = [regex rangeOfFirstMatchInString:string
options:0 range:NSMakeRange(0, [string length])];
NSRange keyPathRange = NSMakeRange(matchRange.location + 2, matchRange.length - 4);
NSString *keyPath = [string substringWithRange:keyPathRange];
NSString *newSubstring = [myDictionary valueForKeyPath:keyPath];
[string replaceCharactersInRange:matchRange withString:newSubstring];
I haven't tested this code.
How about a (recursive ... cool) category method on NSMutableDictionary like this:
- (void)setObject:(id)object forCompoundKey:(NSString *)compoundKey {
NSArray *keys = [compoundKey componentsSeparatedByString:#"."];
if ([keys count] == 1) {
return [self setObject:object forKey:compoundKey];
}
// get the first component of the key
NSString *key = [keys objectAtIndex:0];
// build the remaining key with the remaining components
NSRange nextKeyRange;
nextKeyRange.location = 1;
nextKeyRange.length = [keys count] - 1;
NSArray nextKeys = [keys subarrayWithRange:nextRange];
NSString *nextKey = [nextKeys componentsJoinedByString:#"."];
NSMutableDictionary *nextDictionary = [NSMutableDictionary dictionary];
[self addObject:nextDictionary forKey:key];
// now the cool part... recursion
[nextDictionary setObject:object forCompoundKey:nextKey];
}
I haven't tested this, but it passes a quick desk check. The objectForCompoundKey: retrieval can be written analogously.
How would I filter through an array and return values that contain a certain part of a string? I have a text box where, for the sake of this example, a user puts in 25, and then hits a "Done" button.
Example:
Original Array {25-1002, 25-1005, 12-1003, 1000-0942, 1-1, 234-25}
I want it to return (after sorting through it and pulling the values I want):
New Array {25-1002, 25-1005}
Please note that in the original array, the last value of 234-25 has a 25 in it as well but is not pulled through. It needs to be the number on the first part of the hyphen.
Thanks in advance!
Use the -filteredArrayUsingPredicate: method, like this:
NSString *searchText = [someField.text stringByAppendingString:#"-"];
newArray = [originalArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^(NSString *value, NSDictionary *bindings){
return ([value rangeOfString:searchText].location != NSNotFound)
}]];
Note that blocks (the ^{} thing) aren’t available pre-iOS 4, so you’ll have to use another of NSPredicate’s constructors if you’re targeting 3.x devices as well.
as an easy to understand answer (not using NSPredicate, which can be intimidating (but is really the correct way to do it)):
NSMutableArray *myNewArray = [[NSMutableArray alloc] init];
for (NSString *string in myArray) {
if([[string substringToIndex:3] isEqualToString #"25-"])
{
[myNewArray addObject:string];
}
}
I am looking for an efficient way to replace URLs in an NSString (or NSMutableString) with the replacement word 'link', for example ...
#"This is a sample with **http://bitbanter.com** within the string and heres another **http://spikyorange.co.uk** for luck"
I would like this to become...
#"This is a sample with **'link'** within the string and heres another **'link'** for luck"
Ideally, I would like this to be some sort of method that accepts regular expressions, but, this needs to work on the iPhone, preferably without any libraries, or, I could be persuaded if the library was tiny.
Other features that would be handy, replace #"OMG" with #"Oh my God", but not when it's part of a word, i.e. #"DOOMGAME" shouldn't be touched.
Any suggestions appreciated.
Regards,
Rob.
This was actually quite a bit of fun to play with and hopefully the solution is somehow what you were looking for. This is flexible enough to cater not only for links, but also other patterns where you may want to replace a word for another using certain conditions:
I have commented most of the code so it should be pretty self explanatory. If not, feel free to leave a comment and I will try my best to help:
- (NSString*)replacePattern:(NSString*)pattern withReplacement:(NSString*)replacement forString:(NSString*)string usingCharacterSet:(NSCharacterSet*)characterSetOrNil
{
// Check if a NSCharacterSet has been provided, otherwise use our "default" one
if (!characterSetOrNil)
characterSetOrNil = [NSCharacterSet characterSetWithCharactersInString:#" !?,()]"];
// Create a mutable copy of the string supplied, setup all the default variables we'll need to use
NSMutableString *mutableString = [[[NSMutableString alloc] initWithString:string] autorelease];
NSString *beforePatternString = nil;
NSRange outputrange = NSMakeRange(0, 0);
// Check if the string contains the "pattern" you're looking for, otherwise simply return it.
NSRange containsPattern = [mutableString rangeOfString:pattern];
while (containsPattern.location != NSNotFound)
// Found the pattern, let's run with the changes
{
// Firstly, we grab the full string range
NSRange stringrange = NSMakeRange(0, [mutableString length]);
NSScanner *scanner = [[NSScanner alloc] initWithString:mutableString];
// Now we use NSScanner to scan UP TO the pattern provided
[scanner scanUpToString:pattern intoString:&beforePatternString];
// Check for nil here otherwise you will crash - you will get nil if the pattern is at the very beginning of the string
// outputrange represents the range of the string right BEFORE your pattern
// We need this to know where to start searching for our characterset (i.e. end of output range = beginning of our pattern)
if (beforePatternString != nil)
outputrange = [mutableString rangeOfString:beforePatternString];
// Search for any of the character sets supplied to know where to stop.
// i.e. for a URL you'd be looking at non-URL friendly characters, including spaces (this may need a bit more research for an exhaustive list)
NSRange characterAfterPatternRange = [mutableString rangeOfCharacterFromSet:characterSetOrNil options:NSLiteralSearch range:NSMakeRange(outputrange.length, stringrange.length-outputrange.length)];
// Check if the link is not at the very end of the string, in which case there will be no characters AFTER it so set the NSRage location to the end of the string (== it's length)
if (characterAfterPatternRange.location == NSNotFound)
characterAfterPatternRange.location = [mutableString length];
// Assign the pattern's start position and length, and then replace it with the pattern
NSInteger patternStartPosition = outputrange.length;
NSInteger patternLength = characterAfterPatternRange.location - outputrange.length;
[mutableString replaceCharactersInRange:NSMakeRange(patternStartPosition, patternLength) withString:replacement];
[scanner release];
// Reset containsPattern for new mutablestring and let the loop continue
containsPattern = [mutableString rangeOfString:pattern];
}
return [[mutableString copy] autorelease];
}
And to use your question as an example, here's how you could call it:
NSString *firstString = #"OMG!!!! this is the best convenience method ever, seriously! It even works with URLs like http://www.stackoverflow.com";
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:#" !?,()]"];
NSString *returnedFirstString = [self replacePattern:#"OMG" withReplacement:#"Oh my God" forString:firstString usingCharacterSet:characterSet];
NSString *returnedSecondString = [self replacePattern:#"http://" withReplacement:#"LINK" forString:returnedFirstString usingCharacterSet:characterSet];
NSLog (#"Original string = %#\nFirst returned string = %#\nSecond returned string = %#", firstString, returnedFirstString, returnedSecondString);
I hope it helps!
Cheers,
Rog
As of iOS 4, NSRegularExpression is available. Amongst other things, you can enumerate all matches within a string via a block, allowing you to do whatever you want to each, or have the regular expression perform some kinds of substitution directly for you.
Direct string substitutions (like 'OMG' -> 'Oh my God') can be performed directly by an NSString, using -stringByReplacingOccurencesOfString:withString:, or replaceOccurrencesOfString:withString:options:range: if your string is mutable.
I’m making a languages application, and I have a long list of vocabulary relating to that language (German, in case anyone was interested). I have the functionality in my app to switch between sorting the tableview by German words, or by english words.
When I use the following:
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:type];
NSString *string = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSArray *array = [[string componentsSeparatedByString:#"\n"] sortedArrayUsingSelector:#selector(compare:)];
it works absolutely perfectly - by which I mean, exactly as expected. What I would like to improve on this however, is that there are certain words, such as verbs or nouns, which are always preceded by prefixes, like “to”, as in “to do something”, or “the” in front of nouns. So what I would like to do is somehow exclude these from my sort, because otherwise I end up with all the verbs being sorted alphabetically under the “t” section in my array, which is not very user friendly.
I’ve looked through the Apple documentation about NSString and NSArray, as this is where the compare function is (unless I’m very much mistaken), and I haven’t found any way that makes sense to me. This is the first time I have done any data handling like this so I may be missing something simple, and so I would really appreciate some help.
Thanks very much
Michaeljvdw
You're on the right track. What you want to use instead of the (built-in) compare method is to write your own method, which can eliminate the "to" or "the" bits if they exist, and then use the existing compare method.
Your call would look something like this:
NSArray *array = [[string componentsSeparatedByString:#"\n"] sortedArrayUsingSelector:#selector(myCompare:)];
Using a custom category you give to NSString with the following methods:
// This method can be exposed in a header
- (NSComparisonResult)myCompare:(NSString*)aString
{
NSString* selfTrimmed = [self removeArticles];
NSString* aStringTrimmed = [s2 removeArticles];
return [self compare:aString];
}
// This method can be kept private in the .m implementation
- (NSString*)removeArticles
{
NSRange range = NSMakeRange(NSNotFound, 0);
if ([self hasPrefix:#"to "])
{
range = [self rangeOfString:#"to "];
}
else if ([self hasPrefix:#"the "])
{
range = [self rangeOfString:#"the "];
}
if (range.location != NSNotFound)
{
return [self substringFromIndex:range.length];
}
else
{
return self;
}
}
You might have some luck with localizedCompare: or localizedStandardCompare:, but I don't think that either of these will strip out articles and prepositions like you want. Instead, you will probably have to define a category on NSString that provides the specific style of sorting you're looking for:
#interface NSString (MySortAdditions)
- (NSComparisonResult)compareWithoutArticles:(NSString *)other;
#end
#implementation NSString (MySortAdditions)
- (NSComparisonResult)compareWithoutArticles:(NSString *)other {
NSMutableString *mutableSelf = [NSMutableString stringWithString:self];
[mutableSelf
replaceOccurrencesOfString:#"das"
withString:#""
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [mutableSelf length])
];
...
// delete articles from 'other' too
NSCharacterSet *trimSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString *trimmedSelf = [mutableSelf stringByTrimmingCharactersInSet:trimSet];
NSString *trimmedOther = ...;
return [trimmedSelf localizedCaseInsensitiveCompare:trimmedOther];
}
#end
You can then use #selector(compareWithoutArticles:) as your sort selector for NSArray.
First, don't use compare:. Use localizedCompare: instead. This is important, because whether á appears just after a or after z as a separate letter depends on the language. localizedCompare: takes care of that.
--edit
As Justin says, localizedStandardCompare: is the selector to be used! I didn't know that method. As written in the documentation, localizedStandardCompare: does more than localizedCompare:, although the document doesn't say exactly what it does.
--end of edit
If you want more, you need to implement that yourself. You can use category for that purpose. First declare it
#interface NSString (MichaelsSuperCompareCategory)
-(NSComparisonResult)michaelsSuperCompare:(NSString*)string;
#end
and then implement it
#interface NSString (MichaelsSuperCompareCategory)
-(NSComparisonResult)michaelsSuperCompare:(NSString*)string{
...
}
#end
This way you can add methods to an existing class. Then you can use
NSArray *array = [[string componentsSeparatedByString:#"\n"]
sortedArrayUsingSelector:#selector(michaelsSuperCompare:)];
It is important to prefix the method name with something distinctive, not to accidentally crash with internal methods used by Apple.
As for the functionality, you need to implement that yourself, as far as I know. You can get the current locale with [NSLocale currentLocale]. You can implement a nicer behavior for the languages you know, and then default to localizedCompare: for unknown languages.
I would somehow do -replaceOccurancesOfStrings on all the data eg "To" -> "" - and then reload the data. (or this can in a text editor)
Another thing to think about is having eg 'to walk' changed to 'walk (to)' which can be done ahead of time (and will also create less confusion for the user as they are scrolling alphabetically).
NSDictionary *story = [stories objectAtIndex: indexPath.row];
cell.text=[NSString stringwithFormat:[story objectForKey#"message];
i dont knw what exaclty "message " contains (what is the meaning of objectForKey#"message")
EDIT CODE
NSString *key =[appDelegate.books objectAtIndex:indexPath.row];
//dict y=#"Name";
NSArray *nameSection = [dict objectForKey:key];
NSDictionary *story = [nameSection objectAtIndex: indexPath.row];
cell.text=[NSString stringwithFormat:[story objectForKey:key]];
NSLog(#"Value Of message: %#", [dict objectForKey:key]);
why my code crashes
If you are more familiar with Java or C# the code is equivalent to something like this:
// Assuming stories is declared as: List<Dictionary<string, string> stories;
Dictionary<string, string> story = stories[indexPath.row];
cell.Text = String.Format(story["message"]);
In Smalltalk-style (and therefore Objective-C too) Object Oriented programming, methods are more like messages to other objects. So a good Objective-C method name should read like an English sentence (Subject-Verb-Object). Because of this working with dictionaries (hash tables) looks like this:
[myDictionary setObject:#"Value" forKey:#"someKey"];
[myDictionary objectForKey:#"someKey"]; // == #"Value"
In Java it would be:
myDictionary.put("someKey", "Value");
myDictionary.get("someKey"); // == "Value"
Notice how the key ("someKey") was the first argument in the Java example. In Objective-C you name your arguments with the method name, hence setObject: forKey:. Also notice that in Objective-C strings start with an # symbol. That's because Objective-C strings are different from regular C strings. When using Objective-C you almost always use Objective-C's # strings.
In C# there is a special syntax for Dictionaries so it becomes:
myDictionary["someKey"] = "Value";
myDictionary["someKey"]; // == "Value"
One important problem that you might encounter if you're new is the problem of native types.
In Java to add an int to a Dictionary you used to have to do:
myDictionary.put("someKey", new Integer(10));
Because the primitive types (int, char/short, byte, boolean) aren't real Objects. Objective-C has this problem too. So if you want to put an int into a dictionary you must use NSNumber like so:
[myDictionary setObject:[NSNumber numberForInt:10]
forKey:#"someKey"];
And you pull out the integer like so:
NSNumber *number = [myDictionary objectForKey:#"someKey"];
[number intValue]; // == 10
EDIT:
Your code might be crashing if you have a '%' character in your string, since stringWithFormat is just like NSLog in that it takes many arguments. So if story["message"] is "Hello" then it'll work fine without extra arguments but if it's "Hello %#" you need to add one argument to stringWithFormat.
NSString *message = #"Hello %#";
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:message forKey:#"message"];
NSString *output = [NSString stringWithFormat:[dict objectForKey:#"message"], #"World!"];
// output is now #"Hello World!".
#"message" is a key for a value stored in the NSDictionary object. The first line declares an NSDictionary named story that appears to come from an array.
If you want to find what value is stored for the key:#"message", consider using:
NSLog(#"Value Of message: %#", cell.text);
Run and check the console to see the output. (SHIFT + COMMAND + Y) in XCode will bring up the console, if that's what you are using. If you are unfamiliar with NSArrays/NSDictionaries, give Apple's Documentation a look.
I'm just guessing at all of this since that is a very limited sample of code. Try submit more code when you ask a question so that the viewers can get a better idea of your questions.
That is an example of key-value coding, and a lot of information is available on the Apple dev site if you're interested:
http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/KeyValueCoding/KeyValueCoding.html