CS193p-Adding backspace to a calculator - objective-c

I recently started following the online course on iPhone development from Stanford University on iTunes U.
I'm trying to do the homework assignments now for the first couple of lectures. I followed through the walkthrough where I built a basic calculator, but now I'm trying the first assignment and I can't seem to work it out. It's a follows:
Implement a “backspace” button for the user to press if they hit the wrong digit button. This is not intended to be “undo,” so if they hit
the wrong operation button, they are out of luck! It’s up to you to
decided how to handle the case where they backspace away the entire
number they are in the middle of entering, but having the display go
completely blank is probably not very user-friendly.
I followed this: Creating backspace on iOS calculator
So the code is
-(IBAction)backspacePressed:(UIButton *)sender {
NSMutableString *string = (NSMutableString*)[display text];
int length = [string length];
NSString *temp = [string substringToIndex:length-1];
[display setText:[NSString stringWithFormat:#"%#",temp]];
}
My question is shouldn't I check whether my last is a digit or operand? If operand, no execution and if digit, remove it...

First of all, there are several unnecessary steps in that code... And to answer your question, yes, you should check for an operand. Here is how I would write that method with a check:
NSString *text = [display text];
int length = [text length];
unichar c = [text characterAtIndex:length];
NSCharacterSet *digits = [NSCharacterSet decimalCharacterSet];
if ([digits characterIsMember:c] || c == '.') {
NSString *temp = [[display text] substringToIndex:length-1];
[display setText:temp];
}

I'm also going through the fall 2011 class on iTunes U.
The walk through gives us an instance variable userIsInTheMiddleOfEnteringANumber so I just checked to see if that is YES.
- (IBAction)backspacePressed {
if (self.userIsInTheMiddleOfEnteringANumber) {
if ([self.display.text length] > 1) {
self.display.text = [self.display.text substringToIndex:[self.display.text length] - 1];
} else {
self.display.text = #"0";
self.userIsInTheMiddleOfEnteringANumber = NO;
}
}
}

I used the approach taken by Joe_Schmoe, which is straightforward. (just remove characters in the dispaly until you reach the end).
If the user continues pressing 'Clear Error', I removed an item from the stack as well.

I have just started on the course myself, this post is quite old now but my solution might help others, food for thought if nothing else:
- (IBAction)deletePressed:(id)sender
{
NSString *displayText = [display text];
int length = [displayText length];
if (length != 1) {
NSString *newDisplayText = [displayText substringToIndex:length-1];
[display setText:[NSString stringWithFormat:#"%#",newDisplayText]];
} else {
userIsInTheMiddleOfEnteringANumber = NO;
NSString *newDisplayText = #"0";
[display setText:[NSString stringWithFormat:#"%#",newDisplayText]];
}
}

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.

Current text selection in CustomKeyBoardExtension

I'm trying to write Custom Keyboard Extension.
I'm looking for the way to know where the cursor is on UITextField,UITextView...etc in CustomKeyboardExtension ... but I don't see anything like that.
I saw SwiftKey app (http://swiftkey.com) can do that (or do something like that). When I change the cursor, suggestion-text will change (see below pictures).
Q: How can we get current text selection?
...
UPDATE: 29/09/2014
Ok, I'm so foolish. We can use documentContextBeforeInput, documentContextAfterInput methods of textDocumentProxy property. I thought that "Before","After" are about the time. Actually it's about the position.
Sorry all! I wasted your time :(
Create lastWordBeforeInput method...
-(NSString *) lastWordBeforeInput{
NSArray *arrayOfSplitsString = [self.textDocumentProxy.documentContextBeforeInput componentsSeparatedByString:#" "];
int countIndex = arrayOfSplitsString.count - 1;
NSCharacterSet *ChSet = [NSCharacterSet alphanumericCharacterSet];
NSCharacterSet *invertedChSet = [ChSet invertedSet];
while (countIndex > 0) {
NSString *lastWordOfSentance = [arrayOfSplitsString objectAtIndex:countIndex--];
if ([[lastWordOfSentance stringByTrimmingCharactersInSet:invertedChSet] rangeOfCharacterFromSet:ChSet].location != NSNotFound) {
return [lastWordOfSentance stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
}
return #"";
}
Then call it with textWillChange/textDidChange as per requirement.
- (void)textWillChange:(id<UITextInput>)textInput {
// The app is about to change the document's contents. Perform any preparation here.
NSLog(#"%#",[self lastWordBeforeInput]);
}
Hope this will help you.

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.

How can I check the value of a string obtained via scripting?

My Mac app gets 2 string values from another app via scripting. Under certain conditions, the sender supplies "0-1". I need to detect this and blank the text box that displays it. The following, which only shows code for the second string, works in the debugger, but not when run outside it.
- (void)controlTextDidChange:(NSNotification *)notification
{
// there was a change in a text control
int tmpInt2 = 0;
NSMutableString *tmp2 = [NSMutableString stringWithString:[inputTextField2 stringValue]];
//NSLog(#"text box changed. value: %i", val);
if ([tmp2 length] > 3)
{
tmp2 = [NSMutableString stringWithString:[tmp2 substringToIndex:[tmp2 length] - 1]];
[inputTextField2 setStringValue:tmp2];
}
if ([tmp2 length] == 3)
{
tmpInt2 = [tmp2 intValue];
if (tmpInt2 > 360 || tmpInt2 < 0 || [tmp2 isEqualToString:#"0-1"])
{
//[self showAlert:#"Heading must be between 000 and 360"];
[inputTextField2 setStringValue:#""];
//[inputTextField2 setBackgroundColor:[NSColor yellowColor]];
[tmp2 setString:#""];
}
}
if ([[inputTextField2 stringValue] rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound)
{
NSLog(#"This is not a positive integer");
//NSMutableString *strippedString = [NSMutableString stringWithCapacity:tmp.length];
[inputTextField2 setStringValue:#""];
//[[inputTextField2 cell] setBackgroundColor:[NSColor yellowColor]];
[tmp2 setString:#""];
}
/*
if ([tmp2 isEqualToString:#"0-1"])
{
[inputTextField2 setStringValue:#""];
[tmp2 setString:#""];
}
*/
if ([tmp2 rangeOfString:#"-"].location == NSNotFound) {
NSLog(#"string does not contain 0-1");
} else {
NSLog(#"string contains 0-1!");
[inputTextField2 setStringValue:#""];
[tmp2 setString:#""];
}
}
You should look into #trojanfoe's suggestion of using NSFormatter or one of its pre-defined subclasses. However you appear to misunderstand the purpose of NSMutableString, so I offer the following version of your code with some comments embedded. The text field used for the test was given a placeholder value of "Enter Heading", and it is assumed ARC is enabled. Modern property access syntax is used (object.property). HTH.
- (void)controlTextDidChange:(NSNotification *)notification
{
// there was a change in a text control
NSTextField *inputTextField = notification.object; // get the field
NSTextFieldCell *fieldCell = inputTextField.cell; // and its cell - we use the placeholder text for feedback in this sample
fieldCell.placeholderString = #"Enter heading"; // user has typed, restore default message
NSString *contents = inputTextField.stringValue; // an NSMutableString is not required, you never mutate this string
NSUInteger length = contents.length;
if (length > 3)
{
// remove last character - did you mean to truncate to three characters?
inputTextField.stringValue = [contents substringToIndex:length - 1];
}
else if (length == 3)
{
int tmpInt = contents.intValue;
if (tmpInt > 360 || tmpInt < 0 || [contents isEqualToString:#"0-1"])
{
fieldCell.placeholderString = #"Heading must be between 000 and 360"; // inform user why field was blanked
inputTextField.stringValue = #"";
}
}
else if ([contents rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound)
{
// you might want different logic here
// if a user types "12Y" you delete everything, deleting just the "Y" might be more friendly
// ("Y" picked as an example as it could be a miss hit for the 6 or 7 keys)
fieldCell.placeholderString = #"Enter a positive integer"; // inform user why field was blanked
inputTextField.stringValue = #"";
}
}
Addendum - Comment Followup
Exactly what inputs you are expecting and what you wish to do with them is unclear. The first if just removes the last character from strings longer than 3 without doing any other checks. However I may have misinterpreted your intentions here, you have have intended to continue processing after that first if, e.g. something like:
...
if (length > 3)
{
// remove last character - did you mean to truncate to three characters?
contents = [contents substringToIndex:length - 1];
length -= 1;
}
if (length == 3)
{
...
Which means if your input is longer than 3 characters you remove the last (did you not want to simply truncate to 3? If so just change those two lines of code to do so) and then you continue with the following if/else.

Weird cocoa bug?

Hey folks, beneath is a piece of code i used for a school assignment.
Whenever I enter a word, with an O in it (which is a capital o), it fails!
Whenever there is one or more capital O's in this program, it returns false and logs : sentence not a palindrome.
A palindrome, for the people that dont know what a palindrome is, is a word that is the same read left from right, and backwards. (e.g. lol, kayak, reviver etc)
I found this bug when trying to check the 'oldest' palindrome ever found: SATOR AREPO TENET OPERA ROTAS.
When I change all the capital o's to lowercase o's, it works, and returns true.
Let me state clearly, with this piece of code ALL sentences/words with capital O's return false. A single capital o is enough to fail this program.
-(BOOL)testForPalindrome:(NSString *)s position:(NSInteger)pos {
NSString *string = s;
NSInteger position = pos;
NSInteger stringLength = [string length];
NSString *charOne = [string substringFromIndex:position];
charOne = [charOne substringToIndex:1];
NSString *charTwo = [string substringFromIndex:(stringLength - 1 - position)];
charTwo = [charTwo substringToIndex:1];
if(position > (stringLength / 2)) {
NSString *printableString = [NSString stringWithFormat:#"De following word or sentence is a palindrome: \n\n%#", string];
NSLog(#"%# is a palindrome.", string);
[textField setStringValue:printableString];
return YES;
}
if(charOne != charTwo) {
NSLog(#"%#, %#", charOne, charTwo);
NSLog(#"%i", position);
NSLog(#"%# is not a palindrome.", string);
return NO;
}
return [self testForPalindrome:string position:position+1];
}
So, is this some weird bug in Cocoa?
Or am I missing something?
B
This of course is not a bug in Cocoa, as you probably knew deep down inside.
Your compare method is causing this 'bug in Cocoa', you're comparing the addresses of charOne and charTwo. Instead you should compare the contents of the string with the isEqualToString message.
Use:
if(![charOne isEqualToString:charTwo]) {
Instead of:
if(charOne != charTwo) {
Edit: tested it in a test project and can confirm this is the problem.
Don't use charOne != charTwo
Instead use one of the NSString Compare Methods.
if ([charOne caseInsensitiveCompare:charTwo] != NSOrderedSame)
It may also have to do with localization (but I doubt it).