Issue when differentiating metadata for UILabel? - ios

I am fetching meta data for the radio streams that currently being played in my iOS app, (Artist Name, track name). However this meta data comes in one piece i.e = "FRANK SINATRA - THEME FROM NEW YORK, NEW YORK".
While this is okay, It would look much nice if I had 2 different labels styled differently one saying "FRANK SINATRA" while the other would read "THEME FROM NEW YORK, NEW YORK".
To do this, I have to get rid of the hyphen,
_metaData = infoString; // THIS IS THE OLD 1- LABEL FOR DATA ALL WAY
// THIS IS THE CODE I AM IMPLEMENTING TO GET RID OF THE HYPHEN
NSUInteger xxx = [_metaData rangeOfString:#"-"].location;
NSUInteger xxxPlustTwo = xxx + 2;
NSString *xxxx = [_metaData substringToIndex:xxx];
self.artistName.text=xxxx;
NSString *trackInformation = [_trackName substringFromIndex:xxxPlustTwo];
self.soundInfoMeta.text = trackInformation;
And just like that I am able to break the 2 apart, problem is that when the station goes to commercial my app immediately crashes. The following is the error I receive on Xcode:
***Terminating app due to uncaught exception ‘NSRangeException’, reason: ‘***-[__NSCFString substringToIndex:]: Index 2147483647 out of bounds; string length 17’
What can be causing this problem?
Update: I tried changing the NSUInteger to double but this did not fix the problem, here is the latest crash log:
xxxx NSString * nil 0x00000000
NSObject NSObject
isa Class 0x0
xxx double 2147483647 2147483647
xplus double 2147483649 2147483649
_trackName __NSCFString * #"http://www.181.fm" 0x16d8a890
NSObject NSObject
isa Class __NSCFString 0x39d9a8f8

You're probably getting input that has no hyphen, so the location of the the hyphen is NSNotFound which is defined to be NSIntegerMax, I believe. Your code needs to be robust to this.
This is a bit different from how you're doing it now, but it should work on more types of input:
NSString *infoString = #"ARTIST - TRACK";
NSArray *infoStringComponents = [infoString componentsSeparatedByString:#" - "];
__block NSString *artistString = #"";
__block NSString *trackInfoString = #"";
if ([infoStringComponents count] == 0) {
// This case should only happen when there's no info string at all
artistString = #"Unknown Artist";
trackInfoString = #"Unknown Song";
}
else if ([infoStringComponents count] == 1) {
// If no hyphens just display the whole string as the track info
trackInfoString = infoStringComponents[0];
}
else {
[infoStringComponents enumerateObjectsUsingBlock:^(NSString *component, NSUInteger index, BOOL *stop) {
NSString *trimmedComponent = [component stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (index == 0) {
artistString = trimmedComponent;
}
else {
NSString *buffer = #"";
if ([trackInfoString length] > 0) {
buffer = #" - ";
}
trackInfoString = [trackInfoString stringByAppendingFormat:#"%#%#",buffer,trimmedComponent];
}
}];
}
You could also check if the location property of the range you derive is equal to NSNotFound and then just assume you can't derive an artist out of it and display your _metaData variable in an appropriate label. For example:
NSRange hyphenRange = [infoString rangeOfString:#"-"];
if (hyphenRange.location == NSNotFound) {
// Display only infoString to the user, unformatted into artist / song info
}
else {
// Try the same technique you're attempting now
}

Related

How to filter search within a set of letters in search bar so that each letter typed will reduce the results in objective -c

i have implemented a search bar that searching trough an array of countries(presented in a picker view), the problem is that the user need to type the full country name that it will find it and i want him to be able to type even one letter and it will show the first country that starts with that letter and if types another than it sorts even further etc etc.
Anyone have any ideas??
for(int x = 0; x < countryTable.count; x++){
NSString *countryName = [[countryTable objectAtIndex:x]objectForKey:#"name"];
if([searchedStr isEqualToString:countryName.lowercaseString]){
[self.picker selectRow:i inComponent:0 animated:YES];
flag.image = [UIImage imageNamed:[[countryTable objectAtIndex:i]objectForKey:#"flag"]];
}
}
There's a method on NSArray called filteredArrayUsingPredicate: and a method on NSString called hasPrefix:. Together they do what you need...
NSString *userInput = //... user input as lowercase string. don't call this countryName, its confusing
NSPredicate *p = [NSPredicate predicateWithBlock:^BOOL(id element, NSDictionary *bind) {
NSString countryName = [[element objectForKey:#"name"] lowercaseString];
return [countryName hasPrefix:userInput];
}];
NSArray *filteredCountries = [countryTable filteredArrayUsingPredicate:p];
If you're on iOS 8 or OS X Yosemite, you can do:
NSString *country = countryName.lowercaseString; //"england"
NSString *needle = #"engl";
if (![country containsString:needle]) {
NSLog(#"Country string does not contain part (or whole) of searched country");
} else {
NSLog(#"Found the country!");
}
Else, if on versions below iOS 8:
NSString *country = countryName.lowercaseString; //"england"
NSString *needle = #"engl";
if ([country rangeOfString:needle].location == NSNotFound) {
NSLog(#"Country string does not contain part (or whole) of searched country");
} else {
NSLog(#"Found the country!");
}
Lastly, just iterate through all possible countries and apply this to them all. There might exist more robust solutions out there (like danh's solution with some smaller modifications), but this is by far the easiest to start with.

Delete Button Calculator *Help*

I have this code for my calculator that lets me delete one number at a time!
- (IBAction)deleteButton:(id)sender {
NSString *string = [Screen text];
int length = [string length];
NSString *temp = [string substringToIndex:length-1];
if ([temp length] == 0) {
temp = #"0";
}
[Screen setText:temp];
}
It works, but whenever I enter another number, it resets back to the whole thing, so lets take this as an example.
I have the number 5678, (I deleted 678), So my new number is 5, (Now if I press another number), it goes back to 56781 ( 1 being the new number)
Heres my full code for my project! ---> http://txt.do/oduh
As seen in your code, you are using SelectNumber to store value everywhere, but in deleteButton method you are not storing new value in SelectNumber. So you need to set the new value in deleteButton.
- (IBAction)deleteButton:(id)sender {
NSString *string = [Screen text];
int length = [string length];
NSString *temp = [string substringToIndex:length-1];
if ([temp length] == 0) {
temp = #"0";
}
SelectNumber = [temp intValue]; // set new value here
[Screen setText:temp];
}

How to randomize letters correctly from an NSString

I am creating a word scrambler and I am having issues randomizing the letters. When the letters get randomized, it doesn't make sense.
For example, the word PARK shows as AAPA. So, as you can tell it won't make sense for the user when it is time to unscramble.
Just so you know, I am using a .plist file to hold the words.
This is the code I am using to randomize the letters:
_words = [NSMutableArray arrayWithCapacity:scramblelength];
for (int i=0;i<scramblelength;i++) {
NSString *letter = [scramble substringWithRange:[scramble rangeOfComposedCharacterSequenceAtIndex:arc4random()%[scramble length]]];
Then, I am creating UIImageViews to display the scrambled words:
if (![letter isEqualToString:#""]) {
GameView *boxes = [[GameView alloc] initWithLetter:letter andSideLength:boxSide];
boxes.center = CGPointMake(xOffset + i*(boxSide + kTileMargin), kScreenHeight/4*3);
[self.scrambleView addSubview:boxes];
[_words addObject:boxes];
What am I doing wrong here? I would like for the letters in the scrambled words to make sense.
Please help, I am stuck on this one!
Thanks!
As long as your string length will fit in 32 bits, this should be fine. If not, I would replace arc4random_uniform with a uniform random number generator in C++ and compile this as an Objective-C++ module.
The code simply iterates through the string, and swaps each composed character sequence with some random composed character sequence from the same string.
Sorry, that's what happens when you are arrogant and just type out code. Let me know if you have trouble with this one...
For much larger strings, there is a more efficient way, but this seems to do the trick.
NSMutableString category...
#interface NSMutableString (Scramble)
- (void)scramble;
#end
#implementation NSMutableString (Scramble)
static void
swapRanges(NSMutableString *string, NSRange iRange, NSRange jRange)
{
// Need to replace the "trailing" component first
if (NSEqualRanges(iRange, jRange)) return;
if (iRange.location > jRange.location) {
NSRange tmpRange = iRange;
iRange = jRange;
jRange = tmpRange;
}
NSString *iString = [self substringWithRange:iRange];
NSString *jString = [self substringWithRange:jRange];
[string replaceCharactersInRange:jRange withString:iString];
[string replaceCharactersInRange:iRange withString:jString];
}
- (void)scramble
{
for (NSUInteger i = 0; i < self.length; ++i) {
NSRange iRange = [self rangeOfComposedCharacterSequenceAtIndex:i];
NSUInteger j = arc4random_uniform(self.length);
NSRange jRange = [self rangeOfComposedCharacterSequenceAtIndex:j];
swapRanges(self, iRange, jRange);
}
}
#end
NSString category...
#interface NSString (Scramble)
- (NSString*)scrambledString;
#end
#implementation NSString (Scramble)
- (NSString *)scrambledString
{
NSMutableString *result = [self mutableCopy];
[result scramble];
return [result copy];
}
#end
Sample use...
[someMutableString scramble];
NSString *mixedUp = [someString scrambledString];
Or, if you are comfortable with C++, convert to a std::wstring, call std::random_shuffle, then convert that to a NSString. Lots less bugs when using proven, well tested code.
When you are getting a random letter, you need to do something to remove that letter from your NSMutableArray (ie the word's letters when in order). So as you iterate through the word, each time there are fewer characters remaining. Right now, from your limited code block (the first one), it appears you might not be doing that. You want something like "[_words removeObjectAtIndex:letterIndex]" and you would also want to iterate from number of letters down to zero as you remove items from the array also: for (int i=[_words count]; i > [_words count]; i--) because you need to go from 4 letters down to 0 letters left.
So, I'm sure there are more efficient ways to do this, but I go by the rule of not optimizing until you need to. With that in mind, this code appears to work correctly:
- (NSString *)scrambleWord:(NSString *)word {
NSMutableArray *letterArray = [self letterArrayFromWord:word];
NSMutableString *returnValue = [[NSMutableString alloc] init];
do {
int randomIndex = arc4random() % letterArray.count;
[returnValue appendString:letterArray[randomIndex]];
[letterArray removeObjectAtIndex:randomIndex];
if (letterArray.count == 1) {
[returnValue appendString:letterArray[0]];
break;
}
} while (YES);
if ([[returnValue copy] isEqualToString:word]) {
return [self scrambleWord:word];
} else {
return [returnValue copy];
}
}
- (NSMutableArray *)letterArrayFromWord:(NSString *)word {
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < word.length; i = i + 1) {
[array addObject:[NSString stringWithFormat:#"%C", [word characterAtIndex:i]]];
}
return array;
}

Issue with UITextfield into a NSMutableArray

I had this working using NSArray until I realize I need to insert a string into array. Now I'm changing this to a NSMutableArray, but having issues with my syntax. Am I allowed to use componentsJoinedByString in NSMutable arrays?
NSString *item;
int numofSeg;
int needSeg;
if (textfield.text == #"::") {
numofSeg = 0;
}
NSMutableArray *dlist = [[NSMutableArray alloc]init];
//take string from textfield and split it into a list on colons.
dlist = [textfield.text componentsSeparatedByString:#":"];
//run through list to count how many segments. Non blank ones.
for (item in dlist) {
if (item != #""){
numofSeg = numofSeg + 1;
NSLog(#"%#",numofSeg);
}
}
//determine number of segments
needSeg = 8 - numofSeg;
while (needSeg > 0) {
//insert 0000 at blank spot
[dlist insertString:#"0000" atIndex:#""];
needSeg = needSeg - 1;
}
for (item in dlist) {
if (item == #"") {
//remove blank spaces from list
[dlist removeAllObjects:item];
}
}
//join the list of times into a string with colon
NSString *joinstring = [dlist componentsJoinedByString:#":"];
NSLog(#"%#",joinstring);
I don't think NSMutableArray has a method called insertString:atIndex:. You should probably use insertObject:atIndex: instead.
What error do you get exactly?
Edit:
If you only use the NSMutableArray to insert #"0000" before #" ", then you can simply do:
string = [string stringByReplacingOccurrencesOfString:#" " withString:#" 0000"];
or something like that. No need to use NSMutableArray.
I finally figured it out. This is what I ended up using to fix this:
NSString *z = #"0000";
z = [z stringByAppendingString:#""];

Decoding the scanned barcode value to int value

When I scan the barcode and I get some value if it is Equal=2 then I need to display with == and if it is Equal=3 then I need to display with = and if the value is 4 then invalid.
But Scanned Barcode are of integer value -- when decode using NSASCII it is displaying only till value 127 after that it is showing invalid results. Eg: if my Barcode value = 9699 the result value=jem then my added result value=jem= actualstring value=%åasc value id only showing 37
Here is my code:
- (void) readerView:(ZBarReaderView *)view didReadSymbols:(ZBarSymbolSet *)syms fromImage:(UIImage *)img
{
// do something useful with results -- cool thing is that you get access to the image too
for (ZBarSymbol *symbol in syms) {
[resultsBox setText:symbol.data];
if ([resultsBox.text length] == 2) {
addedresult.text = [resultsBox.text stringByAppendingString:#"=="];
} else if ([resultsBox.text length] == 3) {
addedresult.text = [resultsBox.text stringByAppendingString:#"="];
} if ([resultsBox.text length] >= 4) {
addedresult.text = #"Invalid";
}
[Base64 initialize];
NSString *myString = [[NSString alloc]initWithString:addedresult.text];
NSData * data = [Base64 decode:myString];
NSString * actualString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"%#",actualString);
labeltext.text= actualString;
int asc = [actualString characterAtIndex:0];
label.text = [NSString stringWithFormat:#"%d", asc];
[actualString release];
break;
}
}
Since someone revived this question's comments, i'll revive this entire post.
You shouldn't go through NSData to create an NSString from something you already have, and you're probably losing something along the way. Go directly to NSString using stringWithFormat. Also, ASCII will come back and byte you later, if you have a choice, use UTF8.
NSString *actualStringUTF8 = [NSString stringWithFormat:#"%#",[addedresult.text urlEncodeUsingEncoding:NSUTF8StringEncoding]];
NSString *actualStringASCII = [NSString stringWithFormat:#"%#",[addedresult.text urlEncodeUsingEncoding:NSUTF8StringEncoding]];
NSLog(#"%#",actualStringUTF8);
NSLog(#"%c",[actualStringUTF8 UTF8String]); //This is a const char*
Secondly, I looked into the SDK and it says symbol.data is already an NSString*. Depending on what you want, you may not need to do anything. If you do end up needing to change encoding, make sure you understand why you need to (one good reason is "the rest of the application uses NS****StringEncoding").
Also make sure you compare strings the correct "Objective-C" way:
[actualString isEqualToString: testString];
NOT actualString == testString;

Resources