Reading string from array then changing string and outputting new result - objective-c

I am trying to grab a string in my array, change nth letter in the string to a ?, then print the result in a textfield. The problem is my NSMutableArray is being stored into a UIPickerView and I think it would be best just to read the string from the PickerView then change the nth char and print the result. I am struggling with how to grab the string from the picker and change the nth letter.
- (UIView *)viewForRow:(NSInteger)row forComponent:(NSInteger)component
{
if (row == 0 ) {
NSString *originalStringTwo = #"%#", *arrayDictionary;
NSRange two = [originalStringTwo rangeOfString:#"2"];
NSString *newStringTwo = [originalStringTwo stringByReplacingCharactersInRange:two withString:#"?"];
_resultLabel.text = newStringTwo;
}
if (row == 1 ) {
NSString *originalStringThree = #"%#", *arrayDictionary;
NSRange three = [originalStringThree rangeOfString:#"3"];
NSString *newStringThree = [originalStringThreestringByReplacingCharactersInRange:three withString:#"?"];
_resultLabel.text = newStringThree;
}
if ( row == 2 ) {
NSString *originalStringFour = #"%#", *arrayDictionary;
NSRange four = [originalStringFour rangeOfString:#"4"];
NSString *newStringFour = [originalStringFour stringByReplacingCharactersInRange:four withString:#"?"];
_resultLabel.text = newStringFour;
}
return 0;
}

That's not how you create an NSRange. Use NSMakeRange instead.
// Range from index 1 with length 1, eg. 2nd character:
NSRange two = NSMakeRange(2, 1);
NSString *newStringTwo = [originalStringTwo stringByReplacingCharactersInRange:two
withString:#"?"];
Note, that it's never a good idea to grab any data from a GUI element (except user input). What if the text field applies custom formatting?
You should keep your data in your array and use that.

Related

In my macOS application, I am working with UserDefaults dictionaryRepresentation. Sometimes I get strings with unknown encoding. Any suggesition?

I am working with a Objective-C Application, specifically I am gathering the dictionary representation of NSUserDefaults with this code:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *userDefaultsDict = [defaults dictionaryRepresentation];
While enumerating keys and objects of the resulting dict, sometimes I find a kind of opaque string that you can see in the following picture:
So it seems like an encoding problem.
If I try to print description of the string, the debugger correctly prints:
Printing description of obj:
tsuqsx
However, if I try to write obj to a file, or use it in any other way, I get an unreadable output like this:
What I would like to achieve is the following:
Detect in some way that the string has the encoding problem.
Convert the string to UTF8 encoding to use it in the rest of the program.
Any help is greatly appreciated. Thanks
EDIT: Very Hacky possible Solution that helps explaining what I am trying to do.
After trying all possible solutions based on dataUsingEncoding and back, I ended up with the following solution, absolutely weird, but I post it here, in the hope that it can help somebody to guess the encoding and what to do with unprintable characters:
- (BOOL)isProblematicString:(NSString *)candidateString {
BOOL returnValue = YES;
if ([candidateString length] <= 2) {
return NO;
}
const char *temp = [candidateString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
if (ctr != 1 && ctr < [candidateString length]) {
if (temp[ctr] < 0x10 || temp[ctr] > 0x1F) {
returnValue = NO;
}
}
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
free(dest);
return returnValue;
}
- (NSString *)utf8StringFromUnknownEncodedString:(NSString*)originalUnknownString {
const char *temp = [originalUnknownString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
NSString *returnValue = [[NSString alloc] initWithUTF8String:dest];
free(dest);
return returnValue;
}
This returns me a string that I can use to build a full UTF8 string. I am looking for a clean solution. Any help is greatly appreciated. Thanks
We're talking about a string which comes from the /Library/Preferences/.GlobalPreferences.plist
(key com.apple.preferences.timezone.new.selected_city).
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
NSLog(#"%#", city); // \^Zt\^\\^]s\^]\^\u\^V\^_q\^]\^[s\^W\^Zx\^P
(lldb) p [city description]
(__NSCFString *) $1 = 0x0000600003f6c240 #"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10"
What I would like to achieve is the following:
Detect in some way that the string has the encoding problem.
Convert the string to UTF8 encoding to use it in the rest of the program.
&
After trying all possible solutions based on dataUsingEncoding and back.
This string has no encoding problem and characters like \x1a, \x1c, ... are valid characters.
You can call dataUsingEncoding: with ASCII, UTF-8, ... but all these characters will still be
present. They're called control characters (or non-printing characters). The linked Wikipedia page explains what these characters are and how they're defined in ASCII, extended ASCII and unicode.
What you're looking for is a way how to remove control characters from a string.
Remove control characters
We can create a category for our new method:
#interface NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters;
#end
#implementation NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters {
// TODO Remove control characters
return self;
}
#end
In all examples below, the city variable is created in this way ...
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
... and contains #"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10". Also all
examples below were tested with the following code:
NSString *cityWithoutCC = [city stringByRemovingControlCharacters];
// tsuqsx
NSLog(#"%#", cityWithoutCC);
// {length = 6, bytes = 0x747375717378}
NSLog(#"%#", [cityWithoutCC dataUsingEncoding:NSUTF8StringEncoding]);
Split & join
One way is to utilize the NSCharacterSet.controlCharacterSet.
There's a stringByTrimmingCharactersInSet:
method (NSString), but it removes these characters from the beginning/end only,
which is not what you're looking for. There's a trick you can use:
- (NSString *)stringByRemovingControlCharacters {
NSArray<NSString *> *components = [self componentsSeparatedByCharactersInSet:NSCharacterSet.controlCharacterSet];
return [components componentsJoinedByString:#""];
}
It splits the string by control characters and then joins these components back. Not a very efficient way, but it works.
ICU transform
Another way is to use ICU transform (see ICU User Guide).
There's a stringByApplyingTransform:reverse:
method (NSString), but it only accepts predefined constants. Documentation says:
The constants defined by the NSStringTransform type offer a subset of the functionality provided by the underlying ICU transform functionality. To apply an ICU transform defined in the ICU User Guide that doesn't have a corresponding NSStringTransform constant, create an instance of NSMutableString and call the applyTransform:reverse:range:updatedRange: method instead.
Let's update our implementation:
- (NSString *)stringByRemovingControlCharacters {
NSMutableString *result = [self mutableCopy];
[result applyTransform:#"[[:Cc:] [:Cf:]] Remove"
reverse:NO
range:NSMakeRange(0, self.length)
updatedRange:nil];
return result;
}
[:Cc:] represents control characters, [:Cf:] represents format characters. Both represents the same character set as the already mentioned NSCharacterSet.controlCharacterSet. Documentation:
A character set containing the characters in Unicode General Category Cc and Cf.
Iterate over characters
NSCharacterSet also offers the characterIsMember: method. Here we need to iterate over characters (unichar) and check if it's a control character or not.
Let's update our implementation:
- (NSString *)stringByRemovingControlCharacters {
if (self.length == 0) {
return self;
}
NSUInteger length = self.length;
unichar characters[length];
[self getCharacters:characters];
NSUInteger resultLength = 0;
unichar result[length];
NSCharacterSet *controlCharacterSet = NSCharacterSet.controlCharacterSet;
for (NSUInteger i = 0 ; i < length ; i++) {
if ([controlCharacterSet characterIsMember:characters[i]] == NO) {
result[resultLength++] = characters[i];
}
}
return [NSString stringWithCharacters:result length:resultLength];
}
Here we filter out all characters (unichar) which belong to the controlCharacterSet.
Other ways
There're other ways how to iterate over characters - for example - Most efficient way to iterate over all the chars in an NSString.
BBEdit & others
Let's write this string to a file:
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
[city writeToFile:#"/Users/zrzka/city.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
It's up to the editor how all these controls characters are handled/displayed. Here's en example - Visual Studio Code.
View - Render Control Characters off:
View - Render Control Characters on:
BBEdit displays question marks (upside down), but I'm sure there's a way how to
toggle control characters rendering. Don't have BBEdit installed to verify it.

Comparing string to a character of another string?

Here's my program so far. My intention is to have it so the if statement compares the letter in the string letterGuessed to a character in the string userInputPhraseString. Here's what I have. While coding in xCode, I get an "expected '['"error. I have no idea why.
NSString *letterGuessed = userInputGuessedLetter.text;
NSString *userInputPhraseString = userInputPhraseString.text;
int loopCounter = 0;
int stringLength = userInputPhraseString.length;
while (loopCounter < stringLength){
if (guessedLetter isEqualToString:[userInputPhraseString characterAtIndex:loopIndexTwo])
{
//if statement true
}
loopCounter++;
}
You are missing enclosing square brackets on this line:
if (guessedLetter isEqualToString:[userInputPhraseString characterAtIndex:loopIndexTwo])
It should be:
if ([guessedLetter isEqualToString:[userInputPhraseString characterAtIndex:loopIndexTwo]])
Edit that won’t fix your problem, though, because characterAtIndex: returns a unichar, not an NSString.
It's not clear what you are trying to do.. But I suppose that letterGuessed has one character... And that userInputPhraseString has many characters. So you want to know if letterGuessed is inside userInputPhraseString correct?
This is one solution without loops involved.. I replaced the input with fixed values for testing and tested the code.. It works.
NSString *letterGuessed = #"A"; //Change to your inputs
NSString *userInputPhraseString = #"BBBA"; //Since it has A it will be true in the test
NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:letterGuessed];
NSRange range = [userInputPhraseString rangeOfCharacterFromSet:cset];
if (range.location != NSNotFound) { //Does letterGuessed is in UserInputPhraseString?
NSLog(#"YES"); //userInput Does contain A...
} else {
NSLog(#"NO");
}
In regards to your code... I fixed a couple of errors, first you are trying to get a UniChar (Integer) value for the character and want to compare it to a NSString which is an Object. Also fixed a couple of issues with syntax you had and used the right approach which is to return a range of characters. Again for doing what you want to accomplish the example above is the best approach I know, but for the sake of learning, here is your code fixed.
NSString *letterGuessed = #"A"; //Change to your inputs
NSString *userInputPhraseString = #"BBBA"; //Since it has A it will be true in the test
NSInteger loopCounter = 0; //Use NSInteger instead of int.
NSInteger stringLength = userInputPhraseString.length;
BOOL foundChar = NO; //Just for the sake of returning NOT FOUND in NSLOG
while (loopCounter < stringLength){
//Here we will get a letter for each iteration.
NSString *scannedLetter = [userInputPhraseString substringWithRange:NSMakeRange(loopCounter, 1)]; // Removed loopCounterTwo
if ([scannedLetter isEqualToString:letterGuessed])
{
NSLog(#"FOUND CHARACTER");
foundChar = YES;
}
loopCounter++;
}
if (!foundChar) NSLog(#"NOT FOUND");
NSRange holds the position, length.. So we move to a new position on every iteration and then get 1 character.
Also if this approach is what you want, I would strongly suggest a for-loop.

Filtering string for date

I have a string that looks like this:
"51403074 0001048713 1302130446 TOMTOM101 Order
51403074-3-278065518: ontvangen"
This string is filtered from a array that contains similar strings. This string contains some relevant data and some irrelevant data. The only part of the string that is relevant is:1302130446. This number represents a date and time (yy/mm/dd/hh/mm). Because this part is a date and time its not the same every time.
How can I filter this string so that I will have a string that only contains the relevant part.
Sorry still learning IOS dev.
If the date string will always be the third word you could split the NSString into a word array as
NSString *myString = #"This is a test";
NSArray *myWords = [myString componentsSeparatedByString:#" "];
and then access the third item in the array to get the string you wanted.
EDIT (due to comment): To be sure that you get the correct string from your word array you need a unique identifier for your string. It could be that 'TOMTOM101' always follows the date string or something thing else...
**EDIT 2 (due to need of example code)
NSUInteger counter = 0;
NSUInteger dateStringIndex = 0;
for(NSString *str in myWords) {
counter ++;
if([str isEqualToString:#"TOMTOM101"]) {
dateStringIndex = counter - 1;
//We now know which word is the date number, so we can stop looping.
break;
}
}
(not compiled code)
Seems like the relevant string is 10 characters in length.
If the position of this relevant string is fixed in your original string (comes right after TOMTOM),
then you can do something like this:-
iterate through the length of the original string, and look for a block that is = 'TOMTOM'
while iterating add a counter value for count, update it by 1, whenever you meet a 'space'....aka when you meet the first 'space', add a counter value, say count = 1, then when u meet the 2nd space, update count = 2.. and so on,
when you find TOMTOM, your counter will have a value, say =4, therefore you know date string is located after count is set = 3 and before count is set = 4.
while count = 3 do: extract 'relevant string from original string'. When u come across the next 'space', update count = 4, and hence stop extracting from original string.
All you have to do is separate the original string with one-letter space.
NSString *whatever = #"51403074 0001048713 1302130446 TOMTOM101 Order 51403074-3-278065518: ontvangen"
NSString *mydatestring = [self NthString:whatever :#" " :2];
- (NSString *)NthString:(NSString *)source:(NSString *)part:(NSInteger)i {
if (i >= 0) {
NSArray* components = [source componentsSeparatedByString:part];
return [components objectAtIndex:i];
}
else {
return #"";
}
}

How do i compare string and integer?

i have basicly no knowledge of objective c, but how do i make a if statement to see if SourceTypeString is equal to 1 or 2?
NSString* sourceTypeString = [arguments objectAtIndex:2];
UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; // default
NSLog(#"my ns string = %#",sourceTypeString);
//NEWBIE PART
if ((sourceTypeString == 1))
{
NSLog(#"equals 1");
sourceType = (UIImagePickerControllerSourceType)[sourceTypeString intValue];
} else {
NSLog(#"equals 2");
sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
}
//NEWBIE PART
My code crashes and gives me
my ns string = 1
(lldb)
sourceTypeString __NSCFNumber * 0x0013bf80 (int)1
if ([sourceTypeString intValue] == 1)
You can call intValue on an NSString to get its value as an int, if it is possible to do so with the given string. Then you can compare those.
You can't directly compare ints to strings. However, you can use NSString's isEqualToString to check if the first strings value is equal to the string value of the number.
if ([sourceTypeString isEqualToString:#"1"]) {
//
}

Testing the contents of a text field against several different strings

I am having trouble figuring out how to check if a text field contains any of a few different strings. This is what I have, but it does not work:
- (IBAction)Submit:(id)sender {
if ([Input.text isEqualToString:#"axe"/"apple"/"angry"])
Output.text = #"CORRECT";
else Output.text = #"INCORRECT";
If the input text field contains "axe", "apple", or "angry" then the Output Label should display "CORRECT", otherwise it should display "INCORRECT".
I think this is what you want from what you said at the end of the question:
If the input text field = axe, apple or angry then the Output Label = correct but if not output label = Incorrect.
So this is the code:
if([Input.text isEqualToString:#"axe"] || [Input.text isEqualToString:#"apple"] || [Input.text isEqualToString:#"angry"]) {
Output.text = #"CORRECT";
}
else {
Output.text = #"INCORRECT";
}
You were looking for the "or" operator, which is "||".
You also said:
I am having trouble finding out if there is a way to compile a number of words into the same string.
To do that, you can try this:
NSString *str1 = #"axe";
NSString *str2 = #"apple";
NSString *str3 = #"angry";
NSString *combined = [NSString stringWithFormat:#"%# %# %#", str1, str2, str3];
I would suggest a slightly different approach:
// Create an array with all of the acceptable words:
NSArray *correctWords = [NSArray arrayWithObjects:#"axe",
#"apple",
#"angry", nil];
// Check to see if the input text matches one of the correct words
// (stored in the array), and set the Output text:
if ([correctWords containsObject:Input.text]) {
Output.text = #"CORRECT";
} else {
Output.text = #"INCORRECT";
}