iPhone UISlider action method - objective-c

In the current book I am reading, the author implements an IBAction for a slider in the following way (see below V001). To my eye, it seemed a little over complicated so I re-factored the code (V002). Am I right in thinking that sender is a pointer to the object that fired the event? Also, is there any downside to casting sender in the header, rather than leaving it as sender and casting it in the method body?
v001
-(IBAction)sliderChange:(id)sender {
UISlider *slider = (UISlider *)sender;
int progressAsInt = (int)([slider value] + 0.5f);
NSString *newText = [[NSString alloc] initWithFormat:#"%d", progressAsInt];
[sliderLabel setText:newText];
[newText release];
}
v002
-(IBAction)sliderChange:(UISlider*)sender {
NSString *newText = [[NSString alloc] initWithFormat:#"%d",(int)[sender value]];
[sliderLabel setText:newText];
[newText release];
}
gary

id a special type that can hold any object. The nuance is that you don't have well-defined type safety. You can call any selector on any object, and if it exists it will be invoked.
In V001, doing [slider value] instead of [sender value] makes more logical sense which I think is probably why you took to refactoring in the first place because it didn't appear to be called.
In V002 [sender value] retrieves the float property of the same UISlider but hides the fact that you may not be getting a a slider object, and it could be on any object.
This is a matter of style and discrimination. I am pretty diligent in my own code to determine which concrete object I am trying to access a selector, and will even go so far as calling isKindOfClass, and verifying I am calling the selector on the correct UISlider object. To answer your downside question: the type of the object isn't obviated as it should be when using id.
Why? I want multiple sliders on the same view handling the slider event, I don't want one slider to impact the data of both, even if I may want to handle them in the same way.

Related

Display Model's Data in NSMenuItem

I'd like to display some data of my model within a status bar menu. So, I bound my entity-object to the title of an NSMenuItem:
[self.statusMenu setAutoenablesItems:NO];
NSMenuItem * exportMenuItem = [[NSMenuItem alloc] init];
[exportMenuItem bind:#"title" toObject:expo withKeyPath:#"menuItemTitle" options:nil];
[exportMenuItem setEnabled:NO];
[self.statusMenu insertItem:exportMenuItem atIndex:3];
It works fine so far from init. But when I update my Model it does not updates the title of the NSMenuItem.
For reference, the canonical solution to this issue is to implement a class method like this:
+ (NSSet *)keyPathsForValuesAffectingMenuItemTitle
{
return [NSSet setWithObjects: #"propertyMenuItemTitleDependsOn1",
#"propertyMenuItemTitleDependsOn2",
nil];
}
If you implement a method like this, then the framework will handle calling willChangeValueForKey: and didChangeValueForKey: for the key menuItemTitle any time any of the other properties are changed.
Okay I got it:
menuItemTitle is a dynamic getter method which combines two actual properties of expo. So the reason for NSMenuItem's title to not get updated is probably, that menuItemTitle probably never gets actually set.
So how do I tell, that menuItemTitle was changed, when one of my properties was set?
Overriding expo's setters to add [self willChangeValueForKey:#"menuItemTitle"]; and [self didChangeValueForKey:#"menuItemTitle"]; does not work as it causes an endless loop in calling the setter itself again and again.
So here is my solution: I overrode [NSManagedObject setValue:(id)value forKey:(NSString *)key]:
- (void)setValue:(id)value forKey:(NSString *)key {
[self willChangeValueForKey:#"menuItemTitle"];
[super setValue:value forKey:key];
[self didChangeValueForKey:#"menuItemTitle"];
}

String Problems

A very stupid question from a noob.
I have an action, that sets the string of a label.
- (IBAction) changeProductText:(NSString *)str{
lblProductTxt.text = str;
}
This is the string I want to set that to:
TestText = [NSString stringWithFormat:#"Hi"];
And this is how I am doing it:
[self.navigationController pushViewController:nextController animated:YES];
[nextController changeProductText:TestText];
My problem is that it wont set the string to anything is random whats going into the string. It may crash when I click on the cell it may not, so I am doing something wrong.
stringWithFormat gives you an autoreleased format, without seeing more of the code I'm guessing its hitting an autorelease pool and you're trying to access garbage that was your string.
Is this the exact sequence of the statements?
[self.navigationController pushViewController:nextController animated:YES];
[nextController changeProductText:TestText];
I am not 100% sure but I believe that the second line will not be executed before the nextController is being pushed.
Try to reverse them.
(1st create and initialize the nextController)
2nd assign all values that you want to pass down to nextController
3rd push nextViewController on the stack of View Controllers.
[nextController changeProductText:TestText];
[self.navigationController pushViewController:nextController animated:YES];
The only parameter of an IBAction is the sender:
- (IBAction) clickMyButton: (id) sender;
A string is hardly a valid sender for an action, so whatever you are setting to lblProductTxt.text, it is not a string, it is the sender that performs the action.
In your action method, you can of course set lblProductTxt.text. You'll have to find out yourself where you get the string.
Update
From your comments I deduce you don't have an IBAction, you simply have a void method. Your use of IBAction got me on the wrong foot. Declare it like:
- (void) changeProductText: (NSString *) newText;
Omit the (IBAction) designator, as that is only necessary for, well, real IB action methods.
No matter if you use
NSString *testText = [NSString stringWithFormat: #"Hi"];
or
NSString *testText = [NSString stringWithString: #"Hi"];
The result is exactly the same: an autoreleased NSString with the text "Hi". Only the way it was created is slightly different. If one works and the other crashes, then the same thing is wrong and you are just lucky it doesn't crash.
Now what is wrong is impossible to see from what you posted so far.

Warning about making pointer from integer without a cast -- explanation needed

I have this code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic
NSLog(#"didSelectRowAtIndexPath");
//The hud will dispable all input on the view
HUD = [[MBProgressHUD alloc] initWithView:self.view];
// Add HUD to screen
[self.view addSubview:HUD];
// Regisete for HUD callbacks so we can remove it from the window at the right time
HUD.delegate = self;
HUD.labelText = #"Loading Events, Please Wait..";
int i = indexPath.row;
//Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:#selector(loadData:) onTarget:self withObject:i animated:YES];
}
And I get this warning:
warning: passing argument 3 of 'showWhileExecuting:onTarget:withObject:animated:' makes pointer from integer without a cast
Can somebody please explain what I'm doing wrong here? Could someone also briefly explain the situation with ints in Objective-C, coming from Java I find it odd that they are so confusing to use.
The problem is that showWhileExecuting:onTarget:withObject:animated: takes an object as its third argument. To get aroung this, you can wrap integers as objects using the NSNumber class
[NSNumber numberWithInt:i]
You will then have to unwrap the argument in the loadData: method by calling
[argument intValue]
The method takes an object as a third argument (withObject), but you passed an int instead.
Apparently, you provided an integer(int i) instead of an object pointer(type of id). It is not safe. Use NSNumber instead.
int i;
...
NSNumber * numberI = [NSNumber numberWithInt:i];
[HUD showWhileExecuting:#selector(loadData:) onTarget:self withObject:i animated:YES];
All of the answers above are the "correct" ones. I.e. be a good boy and use and NSNumber to pass the value.
However, … the following will work
"damn you, compiler, i'm smarter than you are:"
(cast your integer, totally not a valid object, to id)
[HUD showWhileExecuting:#selector(loadData:)
onTarget:self
withObject:(id)i
animated:YES];
i'm guessing (you didn't say), that your load data method looked like this:
- (void)loadData:(int)i { …
you will see code like this, which is the only reason i mentioned it.
you should be familiar with it.
someone thinks that saving 1 object allocation is going to make their code efficient; don't sweat object allocations, and wrap it up in an NSNumber as shown above
most C compilers will handle this correctly, but it's not guaranteed

Proper Memory Management for Objective-C Method

I'm programming an iPhone app and I had a question about memory management in one of my methods. I'm still a little new to managing memory manually, so I'm sorry if this question seems elementary.
Below is a method designed to allow a number pad to place buttons in a label based on their tag, this way I don't need to make a method for each button. The method works fine, I'm just wondering if I'm responsible for releasing any of the variables I make in the function.
The application crashes if I try to release any of the variables, so I'm a little confused about my responsibility regarding memory.
Here's the method:
FYI the variable firstValue is my label, it's the only variable not declared in the method.
-(IBAction)inputNumbersFromButtons:(id)sender {
UIButton *placeHolderButton = [[UIButton alloc] init];
placeHolderButton = sender;
NSString *placeHolderString = [[NSString alloc] init];
placeHolderString = [placeHolderString stringByAppendingString:firstValue.text];
NSString *addThisNumber = [[NSString alloc] init];
int i = placeHolderButton.tag;
addThisNumber = [NSString stringWithFormat:#"%i", i];
NSString *newLabelText = [[NSString alloc] init];
newLabelText = [placeHolderString stringByAppendingString:addThisNumber];
[firstValue setText:newLabelText];
//[placeHolderButton release];
//[placeHolderString release];
//[addThisNumber release];
//[newLabelText release];
}
The application works fine with those last four lines commented out, but it seems to me like I should be releasing these variables here. If I'm wrong about that I'd welcome a quick explanation about when it's necessary to release variables declared in functions and when it's not. Thanks.
Yes, you need to release them, but you need them just a little longer than beyond the end of your function.
The solution is called autorelease. Just replace release with autorelease and the objects stay around until the program gets back to the runloop.
When the program gets back there, everybody interested in one of the objects should have sent a retain message to it, so the object will not be deallocated when released by the NSAutoreleasePool.
edit actually, looking at your code, there's a lot more wrong with it. E.g. this:
UIButton *placeHolderButton = [[UIButton alloc] init];
placeHolderButton = sender;
doesn't make sense. First you allocate an object, then assign (a pointer to) it to variable placeHolderButton. That's fine.
Then you assign sender to that same variable. The reference to the object you just created is now lost.
Not sure if I get what you want, but this would be better:
-(IBAction)inputNumbersFromButtons:(id)sender {
UIButton *placeHolderButton = sender; // this is still a little useless, but ok
int i = placeHolderButton.tag;
NSString *addThisNumber = [NSString stringWithFormat:#"%i", i];
NSString *placeHolderString = firstValue.text;
NSString *newLabelText = [placeHolderString stringByAppendingString:addThisNumber];
[firstValue setText:newLabelText];
}
No allocs, so no releases necessary. The strings returned by those functions are already added to the autoreleasepool, so they will be deallocated automatically (if needed).
Well. Release them when you are done with them. The sooner the better. Some objects are tricky if you are new to memory management.
Release them in the dealloc method then.
The auto release pool can be handy, some people might disagree according to the performance issues.
you need to release anything containing the word new, alloc/init or copy.
also, you don't need to alloc/init this:
UIButton *placeHolderButton = [[UIButton alloc] init];
placeHolderButton = sender;
another way of doing this is:
UIButton *placeHolderButton = (UIButton *)sender;
in your version, it is allocating an instance with a retain count of +1, but you are immediately replacing the reference, so there is no way of releasing the memory later.
you are creating a lot of instances with alloc/init, and then replacing their references with autoreleased instances.
you could use
NSString *placeHolderString = [placeHolderString stringByAppendingString:firstValue.text];
instead of
NSString *placeHolderString = [[NSString alloc] init];
placeHolderString = [placeHolderString stringByAppendingString:firstValue.text];
which is again replacing a manually managed instance created on the first line, with an autoreleased instance on the second.
infact you could replace every alloc/init in this with the factory method and not have to deal with memory at all in it as they would be autoreleased instances.
-(IBAction)inputNumbersFromButtons:(id)sender {
//cast sender as a UIButton to suppress compiler warning, and allow us to reference it as placeholder button
UIButton *placeHolderButton = (UIButton *) sender;
int i = placeHolderButton.tag;
NSString *addThisNumber = [NSString stringWithFormat:#"%i", i];
[firstValue setText:[firstValue.text stringByAppendingString:addThisNumber]];
}
If you look at the class docs for NSString, any method with a + next to it(ie +stringWithString:(NSString *)string) is a class method, don't use these methods on a reference after you have called alloc/init on it.
I find it puzzling that you use alloc/init on a UIButton.
I always use the factory methods, e.g.
UIButton* aButton = [UIButton buttonWithType:UIButtonTypeCustom];
This returns an autoreleased button which I immediately add to its intended parent view.
Can't confirm it right now, but it looks as if the SDK caches UIButton instances and performs some optimizations behind the scenes. Every time I tried to retain a UIButton ivar, performance has degraded (especially when there is many sub views on screen)

NSMutableDictionary with UIButton* as keys - iPhone development

I'm new to iPhone development and I have a question that may have a very simple answer. I am trying to add buttons to a view and these buttons are associated with a custom class that I defined. When I add the buttons to the view, I would like to know what class these buttons correspond to. This is because when I press the button, I need to get some information about the class, but the receiver of the message is another class. I couldn't find information about an error that I'm getting on the web. The problem I have is that I'm trying to create an NSMutableDictionary where the keys are of type UIButton* and the values are of my custom type:
// create button for unit
UIButton* unitButton = [[UIButton alloc] init];
[sourceButtonMap setObject:composite forKey:unitButton];
Of course, the sourceButtonMap is defined in the class and initialized in the init function as sourceButtonMap = [[NSMutableDictionary alloc] init];
The error I get when I try to add the key-value pair is:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[UIButton copyWithZone:]: unrecognized selector sent to instance 0x3931e90'
Is this happening because I can't store UIButton* as keys?
Can anyone point me why I'm getting this error? Thank you all,
aa
One way I found was to use construct an NSValue to use as the key. To create the that use:
[NSValue valueWithNonretainedObject:myButton].
The caveat here seems to be that if the button is garbage collected, the key will hold an invalid reference.
You can get the reference to the UIButton again while looping through the Dictionary like so:
for (NSValue* nsv in myDict) {
UIButton* b = (UIButton*)[nsv nonretainedObjectValue];
...
}
From Apple docs:
The key is copied (using
copyWithZone:; keys must conform to
the NSCopying protocol).
UIButton does not conform to the NSCopying protocol and so you cannot use it as a key in NSDictionary
I've got a cool trick for this.
I cast the pointer to an int (since thats all a pointer really is) and store it in an NSNumber. Using the NSNumber as a key solves this problem and makes sense fundementally because who cares about storing a copy of the button in the dictionary? It makes more sense to me to store a copy of the pointer's info.
If your like me, you'll probably wrap that bit up into a macro as well. Something like this:
#define BOX_AS_NUM(_ptr_) [NSNumber numberWithInt:(int)_ptr_]
Then it's a little cleaner to use in code...
NSDictionary* btnMap = [NSDictionary dictionaryWithObjectsAndKeys:some_obj, BOX_AS_NUM(some_btn), nil];
-(IBAction)someBtnAction:(id)sender
{
SomeObj* obj = [btnMap objectForKey:BOX_AS_NUM(sender)];
[obj doCoolStuffBecuaseIWasJustClicked];
}
UIButtons have a description property that can be used as a dictionary key:
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] initWithCapacity:1];
UIButton *myButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 10.0f, 10.0f)];
id myObject;
[myDictionary setObject:myObject forKey:myButton.description];
// somewhere else in code
id myLookedUpObject = [myDictionary objectForKey:myButton.description];
// do something with myLookedUpObject