Should I use IBAction with everything that might happen on the screen? - ios

When I created a UISwitch element in my storyboard, and attached it to the .h file, there was this line generated in the controller:
#property (weak, nonatomic) IBOutlet UISwitch *wantHelp;
And I was trying to figure out how to make the system notice when the value was changed, so I tried to manually add something like this below the original statement:
- (IBAction)wantHelp:(id)helpToggle;
and in the controller, I have something like this:
-(IBAction)helpToggle:(id)sender
{
NSLog(#"sender is: %#", sender);
if (wantHelp.on)
{
NSLog(#"yes");
}
else
{
NSLog(#"No");
}
}
What I wanted to do is connect the helpToggle IBAction with the particular UISwitch element, but when I run it, I get an error: unrecognized selector sent to instance which as I understand is means that I called an operation on an incorrect object.
Could someone help me understand what I am doing wrong here?
Thanks!!
This is how my .m file looks like:
#interface PlanBusinessController ()
#end
#implementation PlanBusinessController
#synthesize businessDescription;
#synthesize personName;
#synthesize personEmail;
#synthesize privacy;
#synthesize wantHelp;
-(IBAction)helpToggle:(id)sender
{
NSLog(#"sender is: %#", sender);
if (wantHelp.on)
{
NSLog(#"yes");
}
else
{
NSLog(#"No");
}
}

What's happening is that your UISwitch has been told to call wantHelp: however as no method called this exists in you implementation file (.m file) that is why the 'unrecognized selector sent to instance' error is being called.

So you can have as many IBAction as you want or one it doesnt matter. What matters most is declaring it in your .h file, going into interface builder and linking the desired action to that IBAction.
so you have your IBAction
-(IBAction)WHATEVERNAME:(id)sender
then go to interface builder and click on your File's Owner object(kinda top left). then click on the Connections Inspector (top right)
and drag from the dot where it says WHATEVERNAME to your object (UISwitch wantHelp)
when you do that it will ask you what action you want to receive
take in mind the reason for (id)sender is that you are suppose to cast your object
UISwitch * tempSwitch = (UISwitch*) sender;
so that you have a pointer to the switch object
this is because you could have this same IBAction method handle many different types of objects
although this is all good stuff the actual error came from multiple selectors being called. Once we got into the connection inspector we were able to see the 2 different methods being called by it. One was the valid IBAction and the other was a previously declared method wantHelp

Okay so all the answers failed to give you "the easy way"
Hold down control on your UISwitch and drag it to your .h file.
You will then see a dialog box which will allow you to select the "Connection"
This connection determines if you are creating and IBOutlet or an IBAction.
Once you change it to "Action" you will then have it automatically select "ValueChanged" as the event, and you can give it whatever name you want:
I named it helpSwitchToggled. Then you will have your IBAction in the .h, and in the .m files.
In your .m file you can then write your code for the value changed action:
for copy paste:
- (IBAction)helpSwitchToggled:(id)sender {
if(self.helpSwitch.on)
{
//Do the On stuff
} else {
//Do the Off Stuff
}
}
That is "The easy way" to make an action associated with something on your interface.
The difference between IBAction and IBOutlet has been pointed out in other answers, but basically Actions are responses to control events and Outlets are connections to elements within the view.

IBAction is merely a tag to let Interface Builder know it can attach to that action.
IBAction is defined as void but with the name Interface Builder allows you to drag your connection over it and connect an event from an object to that function.
Other than that Your actions should have meaningful names. helpToggle seems to me to be an on off switch. but usually I would include a bit of description in that message as well. Such as
- (IBAction) helpSwitchToggled:(id) sender{
}
this message gives you a very meaningful name and reading it will tell you exactally what is calling it as well as the type of message that object is sending.
In conjunction I would also have an IBOutlet for that switch called helpSwitch, Like this one.
#property (nonatomic, weak) IBOutlet UISwitch *helpSwitch;
as you can see. that object is clearly a switch. And IBOutlet is actually defined as nothing. but it again is a tag that Interface Builder uses to know you have a property that can be linked to with Interface Builder.
IBAction and IBOutlet are nothing more than Metadata that tells the Interface builder that this is a possible connection. Connecting them is up to you and the compiler treats them like nothing.
I hope this information helped you :)

Related

Adding custom protocol to UITextField

I have some forms for the authentications and signup views and I want that all UITextField inside those forms have a UIButton as an accessory view, just above the keyboard. I want to have the possibility to set the title and the action for this button
Because I want all those text field have one and each will have a title and an action, and to avoid redundancy, I thought about a protocol.
I want something like extending a custom protocol, for example UITextFieldAccessoryViewDelegate and to be conform to some functions like :
-buttonAccessoryView title ... -> String
-didClickOnAccessoryViewButton.. -> ()
My mind is closed. Someone can give me some ideas to do what I want ?
You could use associated objects to solve this problem. This lets you add a property and its synthesized getter/setters.
#interface UITextField(AccessoryButton)
#property(readwrite, strong, nonatomic) UIButton *accessoryButton;
#end
#import<objc/runtime.h>
#implementation UITextField(AccessoryButton)
-(UIButton*) accessoryButton
{
objc_getAssociatedObject(self, #selector(accessoryButton));
}
-(void) setAccessoryButton:(UIButton *)accessoryButton
{
objc_setAssociatedObject(self, #selector(accessoryButton), accessoryButton, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
Include this category into your forms that need the UIButton for the text fields. Then assign action and title to a UIButton like how you normally do.
Considering everything you want to do I think you are better off using a subclass of UITextField rather than an extension.
Extensions can't add instance variables to the objects they extend, but subclasses can. As lead_the_zeppelin points out in his answer, you can use associated objects to simulate adding instance variables with an extension but it seems like you're making it needlessly complicated when a subclass gives you everything you want.

Connecting next field chain for form text fields

I'm trying to connect the text fields in a sign up form so that, upon done being pressed for each, the next field in the form automatically becomes active. To achieve this, I've extended the UITextField class, as follows, in the view controller for the sign up view.
#interface UITextField (Extended)
#property (nonatomic, weak) IBOutlet UITextField* nextField;
#end
I've then set the nextField outlets via interface builder for each field in the form, and have the following code implemented in the view controller, which is also acting as the delegate for the text fields in the form.
- (BOOL) textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
UITextField* field = textField.nextField;
if (field)
[field becomeFirstResponder];
return NO;
}
However when building and running the app, it immediately crashes giving the following error :
*** Terminating app due to uncaught exception 'NSUnknownKeyException',
reason:'[<UITextField 0x78729580> setValue:forUndefinedKey:]:
this class is not key value coding-compliant for the key nextField.'
A bit lost as to how to address this, any help would be appreciated! Thanks in advance.
Just remove all the outlets and recreate them again. You just missed or duplicated one of them, since you have several textfields it is highly possible something has gone awry. And make sure that each nextField really points to next field :)
UPDATE: It's late night so I missed one big problem.
You can't add new properties via category to an existing class like that. The reason is category is not capable of creating a backing variable behind the scenes. (There is a way with associated objects..but it's best not to delve into that black magic.)
Instead of UItextField+extended category, subclass the UITextfield class.
#interface CustomTextField : UITextField
#property (nonatomic, strong) IBOutlet UITextField *nextField;
#end
Then in interface builder, you will have to set that CustomTextfield class in the class inspector for each text field.
The problem is that while you can declare a property in a category, the compiler will not define the property getter and setter for you. You have to define the getter and setter yourself.
You can use an “associated object” to store the property value. See this answer for example code. Since you don't want your nextField property to retain the next field, use OBJC_ASSOCIATION_ASSIGN instead of OBJC_ASSOCIATION_RETAIN_NONATOMIC when setting the associated object.

Update UILabel with results from results of dispatch_async NSURLSession call

I started Objective-C programming a couple weeks ago, so my understanding of how all these pieces fit together & the order they're all happening is still confusing to me. I'm trying to make a JSON API call to one of my apps using NSURLSession. That all works flawlessly, but I want to update a label with a piece of the data that's returned & anytime I look at/try to update the label, I get null.
Some of the SO posts I've found that are similar to my problem include: this, this, this, and this. Coming from the world of Ruby on Rails, I haven't had to deal with async concepts at all, but I know I'm close.
Here's the relevant snippet of code in question:
if (!jsonError) {
NSDictionary *skillBuildData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#:", skillBuildNameLabel.text); // should say "Build Name" but returns null
NSLog(#"%#", skillBuildData[#"name"]); // correctly prints the result
NSLog(#"%#:", skillBuildNameLabel.text); // should have contents of skillBuildData[#"name"] but returns null
skillBuildNameLabel.text = skillBuildData[#"name"]; // obviously results in null, but I don't know why.
});
}
EDIT:
Not sure if it's relevant, but here's the bulk of my ViewController.h to give you an idea of the outlets & actions in this very simple app. One button, one method, the IBOutlet that links the button & JSON call method, and a label:
#interface ViewController : UIViewController {
IBOutlet UILabel *skillBuildNameLabel;
IBOutlet UIButton *getSkillBuildDataButton;
}
- (void)getSkillBuildDataById:(int) skillBuildId;
- (IBAction)buttonPressed;
It seems like I'm very close, I just can't see the link I'm missing. Thank you so much in advance!
EDIT 2:
Check out Ben Kreeger's comment to the response I marked as the answer. I didn't connect the actual label in my storyboard to the outlet I created in my ViewController.h. I had no idea you could drag the line from the element in a storyboard to an actual line of code. That was the missing piece. Looks like I have a lot more to learn about Xcode & Objective-C. Thanks to all who helped me out!
You may have more luck declaring your IBOutlets as properties (#property) instead of as instance variables (see this answer for why weak instead of strong).
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *skillBuildNameLabel;
#property (weak, nonatomic) IBOutlet UIButton *getSkillBuildDataButton;
...
#end
Then you'll be able to reference them as self.skillBuildNameLabel and self.getSkillBuildDataButton in your implementation.
Beware that this self.x notation inside of a callback like that may lead to what's called a retain cycle. If this is the case, Xcode will warn you about this. Here's a bit on retain cycles for you.
Footnote: I rarely ever see (and never write) this syntax anymore for declaring instance variables.
#interface ViewController : UIViewController {
IBOutlet UILabel *skillBuildNameLabel;
IBOutlet UIButton *getSkillBuildDataButton;
}
Use properties instead.
You are doing the logging before setting the text. move
skillBuildNameLabel.text = skillBuildData[#"name"];
to the top of the async block, above the NSLog statements.

Dynamically show/hide a UIPickerView that was created using the interface builder

I'm trying to create an iPad/iPhone app that dynamically creates menus depending on a JSON object that is returned from a RESTful API. Specifically I'm trying to show/hide a UIPickerView that I created using the interface builder. In the properties menu in the interface builder I checked the box "hidden" for that UIPickerView. I used this tutorial to create to UIPickerView. I've set the delegate and data source to the View Controller using the interface builder. I'd like to unhide/show the UIPickerView when a certain condition is met. So far I've tried the following code:
[self.choicePicker setHidden:NO];
self.choicePicker.hidden = NO;
I usually build such object programmatically but I thought I'd try it this way. I've looking through various stackoverflow posts and doing research but I can't seem to find something that works. I'm new to programming in Objective C. Thanks in advance any help is greatly appreciated.
.h file code
#interface slrpViewController : UIViewController<UITextFieldDelegate, UIPickerViewDelegate, UIPickerViewDataSource>
{
IBOutlet UIPickerView *picker_choice;
NSMutableArray *dataArray;
NSMutableData *receivedData;
}
#property(nonatomic, strong) UILabel *nameLabel;
#property(nonatomic, retain) UIPickerView *choicePicker;
.m file code
-(void)buildChoicesMenu:(NSDictionary *)choiceDict{
//in this method we build the choices menu
[self.choicePicker setHidden:NO];
self.choicePicker.hidden = NO;
if (self.choicePicker) self.choicePicker.hidden = !self.choicePicker.hidden;
}
You have two different picker views defined. One a property choicePicker (and an implicit _choicePicker instance variable), and another instance variable picker_choice. It seems you have connected your picker_choice in interface builder, but are trying to modify the property. In fact, if you try to print po self.choicePicker in the debugger, you would get nil, because there is nothing filling it.
Either remove the instance variable, and connect your property in interface builder, or synthesize your property with your instance variable by doing so:
#syntesize choicePicker=picker_choice

Many UIButton in .xib with different tags

I have 17 button in my xib. And I have set them tag values 1 to 17.
Can somebody can tell me, how to connect all 17 buttons with a single variable name in .h and get a particular button in .m with it's tag value.
Thanks
I just tested this, and I know that if you select all your buttons in storyboards, and control drag them to the appropriate controller in an assistant editor you can create a collection of outlets representing all the buttons. The resulting code was:
#property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *buttons;
no its not possible as per my experience. IBOutlet i.e. Interface Builder Outlet always refers to a single connection between an interface component (like button) and a variable in interface (like IBOutlet UIButton *myButton). This one-to-one relation.
There needs to be an IBOutlet per button, so you'll need to create all 17 of them. Connect buttons to outlets individually, and then you will be able to put them into an array inside your initializer if you need them in an array.
You can save on the IBAction methods, though: make one method like this
-(IBAction)buttonClicked:(id)sender {
}
You can connect this method to all buttons, and look at the tag of the (id)sender to decide which button called your action.
You can't give a one reference to 17 button but you can assign one method to 17 buttons like #dasblinkenlight said
You have to just set the IBAction method to all button clicked event
and using tag value you can access the button which you want
-(IBAction)buttonClicked:(id)sender {
int j = [sender tag];
NSLog(#"Clicked Button %i", j);
}

Resources