Simple calculator app crashes when a third number key is punched - iphone

I am a newbie to the iphone app world. So I thought I try my luck with a calculator app.
Unfortunately I am running into an issue where if I press a third key in the calculator the app crashes. Sometimes I get this error EXC_BAD_ACCESS. Here is a code in my CalculatorViewController.m file.
#import "CalculatorViewController.h"
#implementation CalculatorViewController
#synthesize screenText;
- (IBAction)buttonPressed:(id)sender {
NSString *title = [sender titleForState:UIControlStateNormal];
[self collect:title];
}
- (void)collect:(NSString *)digitz {
NSString * newText = nil;
if ([digitz isEqualToString:#"+"]) {
[self add:result];
big_digit = nil;
}
else if ([digitz isEqualToString:#"+"]) {
[self sub:result];
}
else if ([digitz isEqualToString:#"x"]) {
[self multiply:result];
}
else if ([digitz isEqualToString:#"="]) {
[self equate:result];
}
else {
if (big_digit != nil && [big_digit isEqualToString:#"0"] == FALSE)
big_digit = [big_digit stringByAppendingFormat:#"%#",digitz];
else
big_digit = (NSMutableString *) digitz;
result = (int) big_digit;
newText = [[NSString alloc] initWithFormat:
#"%#",big_digit];
}
screenText.text = newText;
[newText release];
}
- (void)add:(int)res {
NSString * newText = nil;
ans = ans + res;
newText = [[NSString alloc] initWithFormat:
#"%#",ans];
screenText.text = newText;
[newText release];
}
Can anyone spot an obvious issue here. Here is the respective header file too.
#import <UIKit/UIKit.h>
#interface CalculatorViewController : UIViewController {
UILabel *screenText;
int number;
int result;
int ans;
//NSString *big_digit;
NSMutableString * big_digit ;
}
#property (nonatomic, retain) IBOutlet UILabel *screenText;
- (IBAction)buttonPressed:(id)sender;
- (void)collect:(NSString *)digitz;
- (void)add:(int)num;
- (void)sub:(int)num;
- (void)multiply:(int)num;
- (void)equate:(int)num;
#end

Well, you probably don't want to just cast a string to an integer (ala (int)big_digit). Instead you want to use [big_digit integerValue];
I think what is happening is that your big_digit property is not retained. In this line, you just assign a string to it that is autoreleased:
big_digit = [big_digit stringByAppendingFormat:#"%#",digitz];
On the next pass through, big_digit is != nil, but [big_digit isEqualToString:#"0"] == FALSE fails because big_digit now points to an invalid memory location.
What you want to do is make big_digit a property in your interface, like so...
#property (nonatomic, retain) NSMutableString *big_digit;
I know reading docs sucks, but looking at your code I think you would really find reading through this useful. Memory management in objective c is quite a bit different from regular old C.
http://developer.apple.com/iphone/library/documentation/cocoa/conceptual/memorymgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW1

In your format strings, you're doing this:
newText = [[NSString alloc] initWithFormat:#"%#", ans];
But according to your #interface, ans is an integer. So that line should read:
newText = [[NSString alloc] initWithFormat:#"%d", ans];
since %d is the format specifier for an integer.

Related

how to update a string variable with another string variable

i have a string declare as such
NSString *str = [[NSString alloc] initWithFormat:#"I require an average GPA of at least %.2f to achieve my Goal of %# this semester - NTU GPA Calculator", pgagoal,(NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker selectedRowInComponent:0]]];
i declared a global variable
NSStrinng *tweetString
and wants to copy the the string in str to tweetString. how should i copy it? since both are pointers, i tried:
tweetString = str;
or
tweetString = [NSString stringWithFormat:#"%#", str];
but it doest work.
EDIT:
my code:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex1{
NSLog(#"buttonindex 1 clicked");
NSString *str2;
NSLog(#"tweetString before if: %#", tweetString);
if (pgagoal < 0) {
NSString *str2 = [[NSString alloc] initWithFormat:#"Confirm, Guarantee, Chop and Stamp! I can achieve my Goal of %# this semester - NTU GPA Calculator", (NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker selectedRowInComponent:0]]];
NSLog(#"tweetString: < 0 %#", str2);
}
else if (pgagoal > 5){
NSString *str2 = [[NSString alloc] initWithFormat:#"Its impossible!, i need an average GPA of at least %.2f to achieve %# this semester - NTU GPA Calculator", pgagoal,(NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker selectedRowInComponent:0]]];
NSLog(#"tweetString: >5 %#", str2);
}
else{
NSString *str2 = [[NSString alloc] initWithFormat:#"I require an average GPA of at least %.2f to achieve my Goal of %# this semester - NTU GPA Calculator", pgagoal,(NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker selectedRowInComponent:0]]];
NSLog(#"tweetString with else: %#", str2);
}
//did i update tweetString correctly?
tweetString = [NSString stringWithString:str2]; <-- stop working from this point EXC_BAD_ACCESS
NSLog(#"tweetString after if else: %#", tweetString);
[self sendEasyTweet:tweetString];
NSLog(#"tweetString: %#", tweetString);
[str2 release];
}
- (void)sendEasyTweet {
// Set up the built-in twitter composition view controller.
TWTweetComposeViewController *tweetViewController = [[TWTweetComposeViewController alloc] init];
// Set the initial tweet text. See the framework for additional properties that can be set.
[tweetViewController setInitialText:tweetString];
// Create the completion handler block.
[tweetViewController setCompletionHandler:^(TWTweetComposeViewControllerResult result) {
switch (result) {
case TWTweetComposeViewControllerResultCancelled:
// The cancel button was tapped.
NSLog(#"Tweet cancelled");
break;
case TWTweetComposeViewControllerResultDone:
// The tweet was sent.
NSLog(#"Tweet done");
break;
default:
break;
}
// Dismiss the tweet composition view controller.
[self dismissModalViewControllerAnimated:YES];
}];
// Present the tweet composition view controller modally.
[self presentModalViewController:tweetViewController animated:YES];
}
EDIT2:
Debbuger output:
2011-12-29 09:54:22.963 GPA[487:707] buttonindex 1 clicked
2011-12-29 09:54:22.966 GPA[487:707] tweetString before if: NTU GPA Calculator <-- i init the string at viewDidLoad
2011-12-29 09:54:22.968 GPA[487:707] tweetString with else: I require an average GPA of at least 1.56 to achieve my Goal of Third Class Honors this semester - NTU GPA Calculator
(gdb)
EDIT3:
my tweetString is declared in view controller.h as
#interface GPAMainViewController : UIViewController <GPAFlipsideViewControllerDelegate>{
UIPickerView * myPicker;
GPAAppDelegate * myPickerDelegate;
IBOutlet UITextField *txtGPA;
IBOutlet UITextField *txtTotalAU;
IBOutlet UITextField *txtRemainingAU;
double pgagoal;
NSString *tweetString;
}
#property (nonatomic, retain) IBOutlet UIPickerView * myPicker;
#property (nonatomic, retain) IBOutlet GPAAppDelegate *myPickerDelegate;
#property (nonatomic, retain) UITextField *txtGPA;
#property (nonatomic, retain) UITextField *txtTotalAU;
#property (nonatomic, retain) UITextField *txtRemainingAU;
#property (nonatomic, retain) NSString *tweetString;
-(IBAction)finishEditing:(id)sender;
-(IBAction)calculateGoal: (id) sender;
-(IBAction)showInfo:(id)sender;
-(IBAction)nextField:(id)sender;
-(IBAction)resetField:(id)sender;
-(void)sendEasyTweet:(id)sender;
The reason that it doesn't work (it is probably crashing with an EXC_BAD_ACCESS) is because the scope of the variable str is only within the block in which it is declared, the block of the else part of your if/else statement. Try something like this:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex1 {
NSString* str; //declare string here so it is in scope the entire method
.
. //your code
.
.
if(yourConditionHere) {
//make sure you initialize str here as well so if the else part of the statement
// isn't executed, you aren't trying to access an uninitialized variable
} else {
str = [[NSString alloc] initWithFormat:#"I require an average GPA of at
least %.2f to achieve my Goal of %# this semester - NTU GPA Calculator",
pgagoal,(NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker
selectedRowInComponent:0]]]; //give str a value
NSLog(#"tweetString with else: %#", str);
} //Variable str is going out of scope here the way you have your code set up now
tweetString = [str copy];
NSLog(#"tweetString after if else: %#", tweetString);
[self sendEasyTweet:tweetString];
NSLog(#"tweetString: %#", tweetString);
[str release];
}
If you want to copy the string, or use the string after you assign it, you either need to copy it or retain it.
NSString *someString = #"This is a string";
NSString *copiedString = [NSString stringWithFormat:#"%#", someString"];
Within a few seconds, both strings will be nil or some other non value. What you must do is :
NSString *someString = #"This is a string";
NSString *copiedString = [NSString stringWithFormat:#"%#", someString"] retain];
By doing this, you will keep both variables in memory as long as they are viable. But in my opinion a better way, especially when dealing with strings is to use copy, like this :
NSString *someString = #"This is a string";
NSString *copiedString = [NSString stringWithFormat:#"%#", someString"] copy];
This will make someString just go away in a few seconds or clock ticks, but copiedString will live on until the function is finished or the class released.
I suspect that you are not getting the string value inside tweetString because both variables have gone from memory when you want to use it.
If you need a variable to stay around, you must copy or retain it.

Memory management of container classes

I've made a container class to store a single tweet. Its initialized by passing in a dictionary object which is a single tweet.
I then store an array of these 'tweets' which I process through to display in a table.
The project is now finished and I am reviewing everything at the moment and I was wondering is there a better way to do this in the future. Is the memory handled correctly. I declare the string member vars with 'copy' and later in the dealloc I use a 'release' rather than just setting them to 'nil'.
Is my init ok or could that be improved?
Tweet.h
#import
#interface Tweet : NSObject
{
NSString * _userName;
NSString * _tweetText;
NSString * _tweetURL;
}
#property (nonatomic, copy) NSString * userName;
#property (nonatomic, copy) NSString * tweetText;
#property (nonatomic, copy) NSString * tweetURL;
- (id) initWithDict:(NSDictionary *)productsDictionary;
#end
Tweet.m
#implementation Tweet
#synthesize userName = _userName;
#synthesize tweetText = _tweetText;
#synthesize tweetURL = _tweetURL;
- (id) initWithDict:(NSDictionary *)productsDictionary
{
NSDictionary *aDict = [productsDictionary objectForKey:#"user"];
self.userName = [aDict objectForKey:#"screen_name"];
self.tweetText = [productsDictionary objectForKey:#"text"];
NSRange match;
match = [self.tweetText rangeOfString: #"http://"];
if (match.location != NSNotFound)
{
NSString *substring = [self.tweetText substringFromIndex:match.location];
NSRange match2 = [substring rangeOfString: #" "];
if (match2.location == NSNotFound)
{
self.tweetURL = substring;
}
else
{
self.tweetURL = [substring substringToIndex:match2.location];
}
}
else
{
self.tweetURL = nil;
}
return self;
}
-(void) dealloc
{
[self.tweetText release];
[self.tweetURL release];
[self.userName release];
[super dealloc];
}
#end
Many Thanks,
Code
At first sight, I see no inherent flaws here. That looks fine. I would prefer to do:
-(void) dealloc
{
[_tweetText release];
[_tweetURL release];
[_userName release];
[super dealloc];
}
But what you do is good as well.

NSString's superclasses go on forever

I've got a method that when put a breakpoint in it and hover over a string, says it's out of scope and you can drill down into the NSString object for what seems like forever. I've tried to put a screen shot... hope it shows up. I think I have some serious memory management problems...
http://web.me.com/gazelips/Site/Blank_files/screenshot.jpg
Here's the whole .h and .m files. The problem occurs in the updateAdvice method.
#import <UIKit/UIKit.h>
#import "SourcePickerViewController.h"
#import "Chemical.h"
#interface AdjustViewController : UIViewController <SourcePickerViewControllerDelegate>{
// IB controls
UITextField *sourceField;
UITextField *volumeField;
UILabel *startingLabel;
UILabel *targetLabel;
UITextView *adviceLabel;
// Setup variables for the kind of chemical
int numberOfComponents;
NSDictionary *dictionaryOfSources;
// Local ivars
float percentRemove;
float gallonsRemove;
float selectedChemSourceAmount;
int delta;
NSString *selectedChemName;
NSString *selectedChemConcentration;
float selectedChemConstant;
BOOL selectedChemIsLiquid;
NSString *compositeName;
NSString *messageBody;
NSString *adviceMessage;
}
#property (nonatomic, retain) IBOutlet UITextField *sourceField;
#property (nonatomic, retain) IBOutlet UITextField *volumeField;
#property (nonatomic, retain) IBOutlet UILabel *startingLabel;
#property (nonatomic, retain) IBOutlet UILabel *targetLabel;
#property (nonatomic, retain) IBOutlet UITextView *adviceLabel;
#property (nonatomic, retain) NSString *selectedChemName;
#property (nonatomic, retain) NSString *selectedChemConcentration;
#property float selectedChemConstant;
#property BOOL selectedChemIsLiquid;
#property (nonatomic, retain) NSString *compositeName;
#property (nonatomic, retain) NSString *messageBody;
#property (nonatomic, retain) NSString *adviceMessage;
#property int numberOfComponents;
#property (nonatomic, retain) NSDictionary *dictionaryOfSources;
- (IBAction)backgroundTap:(id)sender;
//- (IBAction)textFieldDoneEditing:(id)sender;
- (IBAction)startingSliderChanged:(id)sender;
- (IBAction)startingSliderFinishedChanging;
- (IBAction)targetSliderChanged:(id)sender;
- (IBAction)targetSliderFinishedChanging;
- (IBAction)getChemicalSource;
- (void)updateAdvice;
#end
#import "AdjustViewController.h"
#implementation AdjustViewController
#synthesize sourceField;
#synthesize volumeField;
#synthesize startingLabel;
#synthesize targetLabel;
#synthesize adviceLabel;
#synthesize numberOfComponents;
#synthesize dictionaryOfSources;
#synthesize compositeName;
#synthesize messageBody;
#synthesize adviceMessage;
#synthesize selectedChemName;
#synthesize selectedChemConcentration;
#synthesize selectedChemConstant;
#synthesize selectedChemIsLiquid;
- (IBAction)backgroundTap:(id)sender {
[sourceField resignFirstResponder];
[volumeField resignFirstResponder];
[self updateAdvice];
}
- (IBAction)startingSliderChanged:(id)sender {
UISlider *slider = (UISlider *)sender;
int progressAsInt = (int)(slider.value + 0.5f);
NSString *newValue = [[NSString alloc] initWithFormat:#"%d", progressAsInt];
startingLabel.text = newValue;
[newValue release];
}
- (IBAction)targetSliderChanged:(id)sender {
UISlider *slider = (UISlider *)sender;
int progressAsInt = (int)(slider.value + 0.5f);
NSString *newValue = [[NSString alloc] initWithFormat:#"%d", progressAsInt];
targetLabel.text = newValue;
[newValue release];
}
- (IBAction)startingSliderFinishedChanging {
// [self updateAdvice];
}
- (IBAction)targetSliderFinishedChanging {
// [self updateAdvice];
}
// Present the picker for chlorine selection
- (IBAction)getChemicalSource {
SourcePickerViewController *sourcePickerViewController = [[SourcePickerViewController alloc] init];
sourcePickerViewController.delegate = self;
NSLog(#"getChemicalSource setting numberOfComponents %d", self.numberOfComponents);
sourcePickerViewController.numberOfComponents = self.numberOfComponents;
NSLog(#"getChemicalSource sending numberOfComponents %d", sourcePickerViewController.numberOfComponents);
sourcePickerViewController.dictionaryOfSources = self.dictionaryOfSources;
[self presentModalViewController:sourcePickerViewController animated:YES];
[sourcePickerViewController release];
}
- (void)updateAdvice {
NSLog(#"--updateAdvice");
NSLog(#" selectedChemical name = %#", selectedChemName);
NSLog(#" selectedChemical concentration = %#", selectedChemConcentration);
NSLog(#" selectedChemical constant = %1.6f", selectedChemConstant);
NSLog(#" selectedChemical is liquid = %d", selectedChemIsLiquid);
// First check to see if there is a source AND volume, otherwise prompt user to enter them
if ([volumeField.text isEqualToString:#""] || [sourceField.text isEqualToString:#""]) {
adviceMessage = #"Enter a source and volume.";
}
// If there IS a source and volume, calculate!
else {
if ([selectedChemConcentration isEqualToString:#""]) { // If there's no concentration, make a string with just the name
compositeName = selectedChemName;
NSLog(#" compositeName without concentration = %#", compositeName);
}
else { // But if there is a concentration, make a string with the name and concentration and a space between.
compositeName = [[NSString alloc] initWithFormat:#"%# %#", selectedChemName, selectedChemConcentration];
NSLog(#" compositeName with concentration = %# %#", compositeName, selectedChemConcentration);
}
delta = [targetLabel.text intValue] - [startingLabel.text intValue]; // The difference between target and starting levels
NSLog(#" delta = %d", delta);
sourceAmount = delta * [volumeField.text intValue] * sourceConstant; // Calculates the amount of source chemical necessary in ounces
NSLog(#" sourceAmount = %1.1f", sourceAmount);
// If delta is positive, add chemical
if (delta > 0) {
NSLog(#">> Delta > 0");
if (selectedChemIsLiquid) {
if (sourceAmount > 128) { // Amount is more than a gallon
sourceAmount = sourceAmount / 128;
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f gal of ", self.title, delta, sourceAmount];
}
else { // Less than a gallon
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f fl oz of ", self.title, delta, sourceAmount];
}
}
else { // Chemical is a solid
if (sourceAmount > 16) { // Amount is more than a pound
sourceAmount = sourceAmount / 16;
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f lb of ", self.title, delta, sourceAmount];
}
else { // Less than a pound
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f oz of ", self.title, delta, sourceAmount];
}
}
adviceMessage = [[NSString alloc] initWithFormat:#"%#%#.", messageBody, compositeName];
}
// If delta is zero, stay the course
if (delta == 0) {
NSLog(#"== Delta = 0");
adviceMessage = #"You're on target. No action necessary.";
}
// If delta is negative, remove water
if (delta < 0) {
NSLog(#"<< Delta < 0");
adviceMessage = #"You're over target. Remove some water.";
}
}
adviceLabel.text = adviceMessage; // Set the advice label
[messageBody release]; // And get rid of message
[compositeName release];
[adviceMessage release];
}
- (void)viewDidLoad {
NSLog(#"AdjustViewController launched");
sourceField.text = #"";
adviceLabel.text = #"";
percentRemove = 0;
gallonsRemove = 0;
delta = 0;
selectedChemSourceAmount = 0;
// [self updateAdvice];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
sourceField = nil;
volumeField = nil;
startingLabel = nil;
targetLabel = nil;
adviceLabel = nil;
dictionaryOfSources = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[sourceField release];
[volumeField release];
[startingLabel release];
[targetLabel release];
[adviceLabel release];
[dictionaryOfSources release];
[super dealloc];
}
#pragma mark -
#pragma mark Picker View Delegate Methods
// Returns the values from the picker if a source was chosen
- (void)sourcePickerViewController:(SourcePickerViewController *)controller
didSelectSource:(NSString *)source
andConcentration:(NSString *)concentration
andConstant:(float)constant
andIsLiquid:(BOOL)isLiquid {
selectedChemName = source;
selectedChemConcentration = concentration;
selectedChemConstant = constant;
selectedChemIsLiquid = isLiquid;
// Update the source textfield. If concentration is empty, just use the source otherwise concatenate them
if ([selectedChemConcentration isEqualToString:#""]) {
sourceField.text = [[NSString alloc] initWithFormat:#"%#", selectedChemName];
}
else {
sourceField.text = [[NSString alloc] initWithFormat:#"%# %#", selectedChemName, selectedChemConcentration];
}
// [self updateAdvice];
NSLog(#"Returned source = %#, concentration = %#, constant = %1.7f, isLiquid = %d", source, concentration, constant, isLiquid);
NSLog(#"selectedChemical.chemName = %#, chemConcentration = %#, chemConstant = %1.7f, chemIsLiquid = %d", selectedChemName, selectedChemConcentration, selectedChemConstant, selectedChemIsLiquid);
[self dismissModalViewControllerAnimated:YES];
}
// Returns from the picker without choosing a new source
- (void)sourcePickerViewController:(SourcePickerViewController *)controller
didSelectCancel:(BOOL)didCancel {
// [self updateAdvice];
NSLog(#"Returned without selecting source");
[self dismissModalViewControllerAnimated:YES];
}
#end
What you're seeing there is not an infinite chain of superclasses (if you look at the actual address you'll see it does not change after the second time), you're actually looking at the NSObject metaclass. Have a quick read through that link—wonderfully provided by Matt Gallagher—and it will explain what you're seeing in the debugger. NSObject's metaclass's metaclass is itself.
This is actually quite common and not necessarily anything to do with a bug in your code.
If you right click on the string object in the debugger window and select "print description to console", it should work just fine.
I'm just looking at your code anyway. What the other two people said hold... however, memory management issues:
if (sourceAmount > 16) { // Amount is more than a pound
sourceAmount = sourceAmount / 16;
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f lb of ", self.title, delta, sourceAmount];
}
else { // Less than a pound
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f oz of ", self.title, delta, sourceAmount];
}
[...]
[messageBody release]; // And get rid of message
[compositeName release];
[adviceMessage release];
I think it would be better for you to simply write
self.messageBody = [NSString stringWithFormat:#"To increase %#...", self.title];
This automatically invokes the accessor methods which you synthesized using #synthesize messageBody.
It will automatically release the old string, and retain the new one.
What you are doing right now is allocating a new string without releasing the old string. And why do that yourself when you have already synthesized the accessor methods?
Finally, don't forget to properly release the messageBody in dealloc, where you should be releasing it.
- (void)dealloc
{
[messageBody release];
[everythingElseIHaveToRelease release];
[super dealloc];
}
Hope that helps!
H

Why are my properties `out of scope`?

I have a method to generate some strings based on the values of some variables I get from I picker controller that is presented modally. I get the values back in the delegate method fine and I can assign them to some properties in my view controller successfully. When I call my method to do the updating, I send the values to the console via NSLog and they show up fine there too. But if I put a breakpoint at the end of my method and look at their values just below the NSLogs, the hover-message says out of scope.
EDIT: I went ahead and added the rest of the head and implementation. Maybe someone can see
#import <UIKit/UIKit.h>
#import "SourcePickerViewController.h"
#import "Chemical.h"
#interface AdjustViewController : UIViewController <SourcePickerViewControllerDelegate>{
// IB controls
UITextField *sourceField;
UITextField *volumeField;
UILabel *startingLabel;
UILabel *targetLabel;
UITextView *adviceLabel;
// Setup variables for the kind of chemical
int numberOfComponents;
NSDictionary *dictionaryOfSources;
// Local ivars
float percentRemove;
float gallonsRemove;
float selectedChemSourceAmount;
int delta;
NSString *selectedChemName;
NSString *selectedChemConcentration;
float selectedChemConstant;
BOOL selectedChemIsLiquid;
NSString *compositeName;
NSString *messageBody;
NSString *adviceMessage;
}
#property (nonatomic, retain) IBOutlet UITextField *sourceField;
#property (nonatomic, retain) IBOutlet UITextField *volumeField;
#property (nonatomic, retain) IBOutlet UILabel *startingLabel;
#property (nonatomic, retain) IBOutlet UILabel *targetLabel;
#property (nonatomic, retain) IBOutlet UITextView *adviceLabel;
#property (nonatomic, retain) NSString *selectedChemName;
#property (nonatomic, retain) NSString *selectedChemConcentration;
#property float selectedChemConstant;
#property BOOL selectedChemIsLiquid;
#property (nonatomic, retain) NSString *compositeName;
#property (nonatomic, retain) NSString *messageBody;
#property (nonatomic, retain) NSString *adviceMessage;
#property int numberOfComponents;
#property (nonatomic, retain) NSDictionary *dictionaryOfSources;
- (IBAction)backgroundTap:(id)sender;
//- (IBAction)textFieldDoneEditing:(id)sender;
- (IBAction)startingSliderChanged:(id)sender;
- (IBAction)startingSliderFinishedChanging;
- (IBAction)targetSliderChanged:(id)sender;
- (IBAction)targetSliderFinishedChanging;
- (IBAction)getChemicalSource;
- (void)updateAdvice;
#end
#import "AdjustViewController.h"
#implementation AdjustViewController
#synthesize sourceField;
#synthesize volumeField;
#synthesize startingLabel;
#synthesize targetLabel;
#synthesize adviceLabel;
#synthesize numberOfComponents;
#synthesize dictionaryOfSources;
#synthesize compositeName;
#synthesize messageBody;
#synthesize adviceMessage;
#synthesize selectedChemName;
#synthesize selectedChemConcentration;
#synthesize selectedChemConstant;
#synthesize selectedChemIsLiquid;
- (IBAction)backgroundTap:(id)sender {
[sourceField resignFirstResponder];
[volumeField resignFirstResponder];
[self updateAdvice];
}
- (IBAction)startingSliderChanged:(id)sender {
UISlider *slider = (UISlider *)sender;
int progressAsInt = (int)(slider.value + 0.5f);
NSString *newValue = [[NSString alloc] initWithFormat:#"%d", progressAsInt];
startingLabel.text = newValue;
[newValue release];
}
- (IBAction)targetSliderChanged:(id)sender {
UISlider *slider = (UISlider *)sender;
int progressAsInt = (int)(slider.value + 0.5f);
NSString *newValue = [[NSString alloc] initWithFormat:#"%d", progressAsInt];
targetLabel.text = newValue;
[newValue release];
}
- (IBAction)startingSliderFinishedChanging {
// [self updateAdvice];
}
- (IBAction)targetSliderFinishedChanging {
// [self updateAdvice];
}
// Present the picker for chlorine selection
- (IBAction)getChemicalSource {
SourcePickerViewController *sourcePickerViewController = [[SourcePickerViewController alloc] init];
sourcePickerViewController.delegate = self;
NSLog(#"getChemicalSource setting numberOfComponents %d", self.numberOfComponents);
sourcePickerViewController.numberOfComponents = self.numberOfComponents;
NSLog(#"getChemicalSource sending numberOfComponents %d", sourcePickerViewController.numberOfComponents);
sourcePickerViewController.dictionaryOfSources = self.dictionaryOfSources;
[self presentModalViewController:sourcePickerViewController animated:YES];
[sourcePickerViewController release];
}
- (void)updateAdvice {
NSLog(#"--updateAdvice");
NSLog(#" selectedChemical name = %#", selectedChemName);
NSLog(#" selectedChemical concentration = %#", selectedChemConcentration);
NSLog(#" selectedChemical constant = %1.6f", selectedChemConstant);
NSLog(#" selectedChemical is liquid = %d", selectedChemIsLiquid);
// First check to see if there is a source AND volume, otherwise prompt user to enter them
if ([volumeField.text isEqualToString:#""] || [sourceField.text isEqualToString:#""]) {
adviceMessage = #"Enter a source and volume.";
}
// If there IS a source and volume, calculate!
else {
if ([selectedChemConcentration isEqualToString:#""]) { // If there's no concentration, make a string with just the name
compositeName = selectedChemName;
NSLog(#" compositeName without concentration = %#", compositeName);
}
else { // But if there is a concentration, make a string with the name and concentration and a space between.
compositeName = [[NSString alloc] initWithFormat:#"%# %#", selectedChemName, selectedChemConcentration];
NSLog(#" compositeName with concentration = %# %#", compositeName, selectedChemConcentration);
}
delta = [targetLabel.text intValue] - [startingLabel.text intValue]; // The difference between target and starting levels
NSLog(#" delta = %d", delta);
sourceAmount = delta * [volumeField.text intValue] * sourceConstant; // Calculates the amount of source chemical necessary in ounces
NSLog(#" sourceAmount = %1.1f", sourceAmount);
// If delta is positive, add chemical
if (delta > 0) {
NSLog(#">> Delta > 0");
if (selectedChemIsLiquid) {
if (sourceAmount > 128) { // Amount is more than a gallon
sourceAmount = sourceAmount / 128;
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f gal of ", self.title, delta, sourceAmount];
}
else { // Less than a gallon
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f fl oz of ", self.title, delta, sourceAmount];
}
}
else { // Chemical is a solid
if (sourceAmount > 16) { // Amount is more than a pound
sourceAmount = sourceAmount / 16;
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f lb of ", self.title, delta, sourceAmount];
}
else { // Less than a pound
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f oz of ", self.title, delta, sourceAmount];
}
}
adviceMessage = [[NSString alloc] initWithFormat:#"%#%#.", messageBody, compositeName];
}
// If delta is zero, stay the course
if (delta == 0) {
NSLog(#"== Delta = 0");
adviceMessage = #"You're on target. No action necessary.";
}
// If delta is negative, remove water
if (delta < 0) {
NSLog(#"<< Delta < 0");
adviceMessage = #"You're over target. Remove some water.";
}
}
adviceLabel.text = adviceMessage; // Set the advice label
[messageBody release]; // And get rid of message
[compositeName release];
[adviceMessage release];
}
- (void)viewDidLoad {
NSLog(#"AdjustViewController launched");
sourceField.text = #"";
adviceLabel.text = #"";
percentRemove = 0;
gallonsRemove = 0;
delta = 0;
selectedChemSourceAmount = 0;
// [self updateAdvice];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
sourceField = nil;
volumeField = nil;
startingLabel = nil;
targetLabel = nil;
adviceLabel = nil;
dictionaryOfSources = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[sourceField release];
[volumeField release];
[startingLabel release];
[targetLabel release];
[adviceLabel release];
[dictionaryOfSources release];
[super dealloc];
}
#pragma mark -
#pragma mark Picker View Delegate Methods
// Returns the values from the picker if a source was chosen
- (void)sourcePickerViewController:(SourcePickerViewController *)controller
didSelectSource:(NSString *)source
andConcentration:(NSString *)concentration
andConstant:(float)constant
andIsLiquid:(BOOL)isLiquid {
selectedChemName = source;
selectedChemConcentration = concentration;
selectedChemConstant = constant;
selectedChemIsLiquid = isLiquid;
// Update the source textfield. If concentration is empty, just use the source otherwise concatenate them
if ([selectedChemConcentration isEqualToString:#""]) {
sourceField.text = [[NSString alloc] initWithFormat:#"%#", selectedChemName];
}
else {
sourceField.text = [[NSString alloc] initWithFormat:#"%# %#", selectedChemName, selectedChemConcentration];
}
// [self updateAdvice];
NSLog(#"Returned source = %#, concentration = %#, constant = %1.7f, isLiquid = %d", source, concentration, constant, isLiquid);
NSLog(#"selectedChemical.chemName = %#, chemConcentration = %#, chemConstant = %1.7f, chemIsLiquid = %d", selectedChemName, selectedChemConcentration, selectedChemConstant, selectedChemIsLiquid);
[self dismissModalViewControllerAnimated:YES];
}
// Returns from the picker without choosing a new source
- (void)sourcePickerViewController:(SourcePickerViewController *)controller
didSelectCancel:(BOOL)didCancel {
// [self updateAdvice];
NSLog(#"Returned without selecting source");
[self dismissModalViewControllerAnimated:YES];
}
#end
Check this: Strange Descriptions for Arrays in XCode debugger
If this is the case, other objects
like NSStrings will be unavailable as
well

Javascript style objects in Objective-C

Background: I use a ton of NSDictionary objects in my iPhone and iPad code. I'm sick of the verbose way of getting/setting keys to these state dictionaries.
So a little bit of an experiment: I just created a class I call Remap.
Remap will take any arbitrary set[VariableName]:(NSObject *) obj selector and forward that message to a function that will insert obj into an internal NSMutableDictionary under the key [vairableName].
Remap will also take any (zero argument) arbitrary [variableName] selector and return the NSObject mapped in the NSMutableDictionary under the key [variableName].
e.g.
Remap * remap = [[Remap alloc] init];
NSNumber * testNumber = [NSNumber numberWithInt:46];
[remap setTestNumber:testNumber];
testNumber = [remap testNumber];
[remap setTestString:#"test string"];
NSString * testString = [remap testString];
NSMutableDictionary * testDict = [NSMutableDictionary dictionaryWithObject:testNumber forKey:#"testNumber"];
[remap setTestDict:testDict];
testDict = [remap testDict];
where none of the properties testNumber, testString, or testDict are actually defined in Remap.
The crazy thing? It works... My only question is how can I disable the "may not respond to " warnings for JUST accesses to Remap?
P.S. : I'll probably end up scrapping this and going with macros since message forwarding is quite inefficient... but aside from that does anyone see other problems with Remap?
Here's Remap's .m for those who are curious:
#import "Remap.h"
#interface Remap ()
#property (nonatomic, retain) NSMutableDictionary * _data;
#end
#implementation Remap
#synthesize _data;
- (void) dealloc
{
relnil(_data);
[super dealloc];
}
- (id) init
{
self = [super init];
if (self != nil) {
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
[self set_data:dict];
relnil(dict);
}
return self;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSString * selectorName = [NSString stringWithUTF8String: sel_getName([anInvocation selector])];
NSRange range = [selectorName rangeOfString:#"set"];
NSInteger numArguments = [[anInvocation methodSignature] numberOfArguments];
if (range.location == 0 && numArguments == 4)
{
//setter
[anInvocation setSelector:#selector(setData:withKey:)];
[anInvocation setArgument:&selectorName atIndex:3];
[anInvocation invokeWithTarget:self];
}
else if (numArguments == 3)
{
[anInvocation setSelector:#selector(getDataWithKey:)];
[anInvocation setArgument:&selectorName atIndex:2];
[anInvocation invokeWithTarget:self];
}
}
- (NSMethodSignature *) methodSignatureForSelector:(SEL) aSelector
{
NSString * selectorName = [NSString stringWithUTF8String: sel_getName(aSelector)];
NSMethodSignature * sig = [super methodSignatureForSelector:aSelector];
if (sig == nil)
{
NSRange range = [selectorName rangeOfString:#"set"];
if (range.location == 0)
{
sig = [self methodSignatureForSelector:#selector(setData:withKey:)];
}
else
{
sig = [self methodSignatureForSelector:#selector(getDataWithKey:)];
}
}
return sig;
}
- (NSObject *) getDataWithKey: (NSString *) key
{
NSObject * returnValue = [[self _data] objectForKey:key];
return returnValue;
}
- (void) setData: (NSObject *) data withKey:(NSString *)key
{
if (key && [key length] >= 5 && data)
{
NSRange range;
range.length = 1;
range.location = 3;
NSString * firstChar = [key substringWithRange:range];
firstChar = [firstChar lowercaseString];
range.length = [key length] - 5; // the 4 we have processed plus the training :
range.location = 4;
NSString * adjustedKey = [NSString stringWithFormat:#"%#%#", firstChar, [key substringWithRange:range]];
[[self _data] setObject:data forKey:adjustedKey];
}
else
{
//assert?
}
}
#end
Cool class. I like it.
I can't think of a way to suppress all your warnings, but we can get it down to one line per property. Add this to your Remap.h:
#define RemapProperty(PROP) \
#interface Remap(PROP) \
#property (nonatomic, retain) id PROP; \
#end \
#implementation Remap(PROP) \
#dynamic PROP; \
#end
Now you can suppress all remap warnings for a given property by putting this at the top of the file that's giving you a warning:
RemapProperty(propertyName);
This requires some extra work, but it gives you dot-syntax as a reward.
remap.propertyName = #"Foo";
With a little more work, you could define a similar macro that adds properties directly to NSDictionary, thereby making the Remap class unnecessary.
If I'm reading your code right, I think a potential problem with this approach might be that you can't have key names such as hash (or other methods from NSObject, assuming your Remap inherits from NSObject). You will end up with the Remap instance's hash value rather than letting Remap look up a key called hash within _data, because [remap hash] will not invoke forwardIncovation:, as far as I can tell.
As an alternative for making dictionaries more like general purpose objects, what I have done is make categories of NSDictionary that wrap getters and setters for specific keys. Just use very descriptive names since you are filling a global namespace.
#interface NSDictionary (MyGetters)
#property (nonatomic,readonly) id something;
-(id) something;
#end
#interface NSDictionary (MyGetters)
-(id) something { return [self objectForKey:#"something"]; }
#end
#interface NSMutableDictionary (MySetters)
-(void) setSomething:(id)inValue;
#end
#interface NSDictionary (MySetters)
-(void) setSomething:(id)inValue { return [self setObject:inValue forKey:#"something"]; }
#end
The only problem with setter properties is that you must define the getter in both categories to avoid a warning.
You still have to declare everything, but you are going to have to do that anyway to get rid of warnings for Remap.