Passing any control as an argument of function - iphone

I want to make a common function which takes a control as an argument (like UITextField, UIButton etc.)
Its working fine if I do like this
- (void) myFunction : (UITextField*) : control
{
}
//But I want to make it common for any control
- (void) myFunction : (`I don't know what to write here`) : control
{
//suppose if control is UITextField, I can set its font and its size.
//something like this
[control setFont:[UIFont fontWithName:#"Verdana" size:12]];
}
Is this possible?

You can also go like this
- (void) myFunction : (id) control
{
if([control isKindOfClass:[UITextField class]]){
// Your textfield condition
}
}

You should pass UIControl, since it's the super class for the controls.
Then in your code, you should use methods like respondsToSelector: to determine whether or not the control passed in can do what you need it to do.
You could check its class type using isKindOfClass: or isMemberOfClass as well.
Once you know which object you're dealing with, you could type cast it to save on some typing and remove any warnings about not responding to selectors, like this:
// decided that it's a UITextField after using `respondsToSelector:` or `isKindOfClass:`
UITextField *aTextField = (UITextField *)control;
This method is known as "duck-typing" - since it's similar to saying "If it walks like a duck and sounds like a duck, it'll probably be a duck".

UIControl is the superclass of UITextField, UIButton, and other controls, so this is what you want:
- (void) myFunction:(UIControl *) control

Yes, It is possible. You can pass UIView as an argument like Below
- (void) myFunction:(UIView*)customView{
if([customView isKindOfClass:[UIImageView class]]){
// This is UIImageview
}
}
Hope this Help.

use generic ID then cast your control and ask if it's a UITextField (or subclass of...)
- (void) myFunction : (id) control
{
//suppose if control is UITextField, I can set its font and its size.
//something like this
(UITextField*)aText = (UITextField*)control;
if ([aText.class isSubclassOfClass:[UITextField class]]) {
[aText setFont:[UIFont fontWithName:#"Verdana" size:12]];
}
}

Related

cast UITextView to UITextField

This may seem like a very simple question, however I haven't been able to discover a simple option yet...
I have a series of UITextFields followed by a UITextView. How can I incorporated the TextFields and the TextView in the same method below.
-(BOOL) textFieldDidBeginEditing:(UITextField *) textField{
textField = activeField;
if([self.textField1 isFirstResponder]){activeField = textField1;}
else if([self.textField2 isFirstResponder]){activeField = textField2;}
else if([self.textField3 isFirstResponder]){activeField = textField3;}
else if([self.textView1 isFirstResponder]){ activeField = textView1;}
[scrollView1 scrollRectToVisible:[activeField frame] animated:YES];
return NO;
}
The last line causes a warning of:
Incompatible pointer types assigning to 'UITextField *_strong' from 'UITextView *_strong'
This is due (I'm sure) to the obvious fact that the UITextField and the UITextView are different Objects... which is fine however is there a way to get around this as I wish to be able to advance through the textFields and TextViews with a next and previous button.
as per this method
-(void) nextTextField:(id)sender{
if([self.textField1 isFirstResponder]){activeField = textField2;}
else if([self.textField2 isFirstResponder]){activeField = textField3;}
else if([self.textField3 isFirstResponder]){activeField = textView1;}
else if([self.textView1 isFirstResponder]){ activeField = textField1;}
}
I was hoping for a casting sort of option however I am a little confused as to how to cast in objective C... This might sound dumb however
activeField = ((UITextField) textView1);
is how I'd have cast in Java but it seems I just can't seem to get he syntax right.
Should I cast to a UIView as they both inherit from that?
Thank you in advance
Okay, so you don't need to store activeField, for this method, so the only relevant code would be your nextTextField: method. Try changing it to:
-(void) nextTextField:(id)sender{
if([self.textField1 isFirstResponder]) {[textField2 becomeFirstResponder];}
else if([self.textField2 isFirstResponder]) {[textField3 becomeFirstResponder];}
else if([self.textField3 isFirstResponder]) {[textView1 becomeFirstResponder];}
else if([self.textView1 isFirstResponder]) {[textField1 becomeFirstResponder];}
}
You don't need to cast to a different type or anything.
I think you have a few issues with your thinking. You will need to go down two levels to get to a common super type. Both UITextField and UITextView are "visible" components so they inherit from UIView. They can both be cast to UIView but not to each other. But this won't help you with your question. I think you may be struggling with delegation also.
The method textFieldDidBeginEditing: is a delegate call and it only works with UITextField. This is why the class that contains your method above should implement UITextFieldDelegate. When you set focus to the UITextField, UITextField first checks to see that the delegate is not nil. If the delegate property holds a class then UITextField checks to see if it implements the textFieldDidBeginEditing: method explicitly. If the method is implemented in the delegate then the UITextField calls the method.
It is no different with the UITextView. However UITextView doesn't even know about the textFieldDidBeginEditing: method. It has its own delegate and its own method that performs the same general function as textFieldDidBeginEditing:. The UITextView version of this method is called textViewDidBeginEditing:. Like the UITextfield the UITextView checks that the delegate is not nil and that it implements textViewDidBeginEditing:. If these requirements are met then UITextView will textViewDidBeginEditing. However UITextView will never call textFieldDidBeginEditing:.
Finally objects cannot be cast into something they're not. They can only be cast as its own type or any of its ancestors.
You will need to set up both each components' method (textFieldDidBeginEditing and textViewDidBeginEditing) for this to work.
Hope this helps.

Passing custom data in [UIButton addTarget]

How do I add custom data while specifying a target in a UIButton?
id data = getSomeData();
[button addTarget:self
action:#selector(buyButtonTapped:event:)
forControlEvents:UIControlEventTouchUpInside];
I want the buyButtonTapped function to look like:
(void) buyButtonTapped: (UIButton *) button event: (id) event data: (id) data
not possible. the action that is triggered by an UIControl can have 3 different signatures.
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event
None of them allows you to send custom data.
Depending on what you want to do you can access the data in different ways.
For example if the button is in a UITableView you could use something like this:
- (IBAction)buttonPressed:(UIButton *)sender {
CGPoint buttonOriginInTableView = [sender convertPoint:CGPointZero toView:tableView];
NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:buttonOriginInTableView];
NSData *customData = [myDataSource customDataForIndexPath:indexPath];
// do something
}
There is always a way to get the data without passing it to the button.
You cannot send extra data to the action method. There are a number of ways to associate the data with the button, although none are particularly straightforward unless the data is a single NSInteger.
You can use the tag property to hold a single NSInteger. This may be all you need, or you could use it to look up an object in an array or dictionary.
You can subclass UIButton to add ivars/properties to store your needed data.
You can use [NSValue valueWithNonretainedObject:button] as a key for a dictionary.
My personal favorite for one-offs, you can use [associative references] to associate the data object with the button.
You can't really do that. What you can do is put the data in a dictionary and use the button to get it later.
E.g.
myDataDict = [NSDictionary dictionaryWithObjectsAndKeys:someData, button, nil];
Then later;
-(void) buttonPress:(id)sender
{
data = [dataDict objectForKey:sender];
}
If your buttons are specified in InterfaceBuilder you can use the 'tag' property of a button to lookup the data, although you will need to convert it to an NSNumber for use with the dictionary.

UIControl subclass is unable to take a target?

I've subclasses UIControl and in it I am sending:
[self sendActionsForControlEvents:UIControlEventValueChanged];
When I create an instance of the object, I add a target as follows:
[starView addTarget:self action:#selector(starRatingChanged:) forControlEvents:UIControlEventValueChanged];
The view shows up fine, and without the target being there the functionality works well. But with adding the target, it crashes. Any ideas why?
My class is declared with:
#interface RMStarRating : UIControl {...}
For what it is worth, I set up my view in - (void)layoutSubviews. Is there another method that I need to subclass in order for the targets to be saved properly or for the targets to be sent the right actions? I thought UIControl handled saving the targets and actions for you.
UPDATE: trying to provide more information
I set the object up as follows:
RMStarRating *starView = [[RMStarRating alloc] initWithFrame:CGRectMake(10, 70, 23*5, 30)];
[starView addTarget:self action:#selector(starRatingChanged:) forControlEvents:UIControlEventValueChanged];
....
[self.view addSubview:starView];
My sendAction, according to Jordan's suggestion:
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
NSLog(#"send action");
[super sendAction:action to:target forEvent:event];
}
My function that calls sendActionsForControlEvents:
- (void)updateValue:(UITouch *)touch {
....
NSLog(#"sendActionsForControlEvents");
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
And the function that should be called (and it is in the header too):
- (void)starRatingChanged:(id)sender {
NSLog(#"star rating changed");
}
And the log just spits out:
2010-10-22 09:45:41.348 MyApp[72164:207] sendActionsForControlEvents
2010-10-22 09:45:41.350 MyApp[72164:207] send action
The debugger has:
Have you tried implementing
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
instead? A good example is located here:
Can I override the UIControlEventTouchUpInside for a UISegmentedControl?
Ok, I figured out what it was. I was releasing my parent class too soon, so there was no object for the message to be sent back to, even though it was showing on screen.
And I ended up not needing the sendAction:to:forEvent.
Jordan, thanks you for your help.

Can I hook into UISearchBar's Clear Button?

I've got a UISearchBar in my interface and I want to customise the behaviour of the the small clear button that appears in the search bar after some text has been entered (it's a small grey circle with a cross in it, appears on the right side of the search field).
Basically, I want it to not only clear the text of the search bar (which is the default implementation) but to also clear some other stuff from my interface, but calling one of my own methods.
I can't find anything in the docs for the UISearchBar class or the UISearchBarDelegate protocol - it doesn't look like you can directly get access to this behaviour.
The one thing I did note was that the docs explained that the delegate method:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText;
is called after the clear button is tapped.
I initially wrote some code in that method that checked the search bar's text property, and if it was empty, then it had been cleared and to do all my other stuff.
Two problems which this though:
Firstly, for some reason I cannot fathom, even though I tell the search bar to resignFirstResponder at the end of my method, something, somewhere is setting it back to becomeFirstResponder. Really annoying...
Secondly, if the user doesn't use the clear button, and simply deletes the text in the bar using the delete button on the keyboard, this method is fired off and their search results go away. Not good.
Any advice or pointers in the right direction would be great!
Thanks!
Found the better solution for this problem :)
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
if ([searchText length] == 0) {
[self performSelector:#selector(hideKeyboardWithSearchBar:) withObject:searchBar afterDelay:0];
}
}
- (void)hideKeyboardWithSearchBar:(UISearchBar *)searchBar{
[searchBar resignFirstResponder];
}
The answer which was accepted is incorrect. This can be done, I just figured it out and posted it in another question:
UISearchbar clearButton forces the keyboard to appear
Best
I've got this code in my app. Difference is that I don't support 'live search', but instead start searching when the user touches the search button on the keyboard:
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
if ([searchBar.text isEqualToString:#""]) {
//Clear stuff here
}
}
Swift version handling close keyboard on clear button click :
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count == 0 {
performSelector("hideKeyboardWithSearchBar:", withObject:searchBar, afterDelay:0)
}
}
func hideKeyboardWithSearchBar(bar:UISearchBar) {
bar.resignFirstResponder()
}
You could try this:
- (void)viewDidLoad
{
[super viewDidLoad];
for (UIView *view in searchBar.subviews){
for (UITextField *tf in view.subviews) {
if ([tf isKindOfClass: [UITextField class]]) {
tf.delegate = self;
break;
}
}
}
}
- (BOOL)textFieldShouldClear:(UITextField *)textField {
// your code
return YES;
}
I would suggest using the rightView and rightViewMode methods of UITextField to create your own clear button that uses the same image. I'm assuming of course that UISearchBar will let you access the UITextField within it. I think it will.
Be aware of this from the iPhone OS Reference Library:
If an overlay view overlaps the clear button, however, the clear button always takes precedence in receiving events. By default, the right overlay view does overlap the clear button.
So you'll probably also need to disable the original clear button.
Since this comes up first, and far as I can see the question wasn't really adequately addressed, I thought I'd post my solution.
1) You need to get a reference to the textField inside the searchBar
2) You need to catch that textField's clear when it fires.
This is pretty simple. Here's one way.
a) Make sure you make your class a , since you will be using the delegate method of the textField inside the searchBar.
b) Also, connect your searchBar to an Outlet in your class. I just called mine searchBar.
c) from viewDidLoad you want to get ahold of the textField inside the searchBar. I did it like this.
UITextField *textField = [self.searchBar valueForKey:#"_searchField"];
if (textField) {
textField.delegate = self;
textField.tag = 1000;
}
Notice, I assigned a tag to that textField so that I can grab it again, and I made it a textField delegate. You could have created a property and assigned this textField to that property to grab it later, but I used a tag.
From here you just need to call the delegate method:
-(BOOL)textFieldShouldClear:(UITextField *)textField {
if (textField.tag == 1000) {
// do something
return YES;
}
return NO;
}
That's it. Since you are referring to a private valueForKey I can't guarantee that it will not get you into trouble.
Best solution from my experience is just to put a UIButton (with clear background and no text) above the system clear button and than connect an IBAction
- (IBAction)searchCancelButtonPressed:(id)sender {
[self.searchBar resignFirstResponder];
self.searchBar.text = #"";
// some of my stuff
self.model.fastSearchText = nil;
[self.model fetchData];
[self reloadTableViewAnimated:NO];
}
Wasn't able to find a solution here that didn't use a private API or wasn't upgrade proof incase Apple changes the view structure of the UISearchBar. Here is what I wrote that works:
- (void)viewDidLoad {
[super viewDidLoad];
UITextField* textfield = [self findTextFieldInside:self.searchBar];
[textfield setDelegate:self];
}
- (UITextField*)findTextFieldInside:(id)mainView {
for (id view in [mainView subviews]) {
if ([view isKindOfClass:[UITextField class]]) {
return view;
}
id subview = [self findTextFieldInside:view];
if (subview != nil) {
return subview;
}
}
return nil;
}
Then implement the UITextFieldDelegate protocol into your class and overwrite the textFieldShouldClear: method.
- (BOOL)textFieldShouldClear:(UITextField*)textField {
// Put your code in here.
return YES;
}
Edit: Setting the delegate on the textfield of a search bar in iOS8 will produce a crash. However it looks like the searchBar:textDidChange: method will get called on iOS8 on clear.

How can I pass a parameter to this function?

I have the following code:
[replyAllBtn addTarget:self.target action:#selector(ReplyAll:) forControlEvents:UIControlEventTouchUpInside];
- (void)replyAll:(NSInteger)tid {
// some code
}
How can I send a parameter to the ReplyAll function?
The replyAll method should accept (id)sender. If a UIButton fired the event, then that same UIButton will be passed as the sender. UIButton has a property "tag" that you can attach your own custom data to (much like .net winforms).
So you'd hook up your event with:
[replyAllBtn addTarget:self.target action:#selector(ReplyAll:) forControlEvents:UIControlEventTouchUpInside];
replyAllBtn.tag=15;
then handle it with:
(void) ReplyAll:(id)sender{
NSInteger *tid = ((UIControl*)sender).tag;
//...
A selector function will normally be defined as such:
- (void) ReplyAll:(id)sender;
So the only parameter an action will ever receives is the actual control that called it.
You could just add a property to your control that can be read in replyAll
If you want to send an int value, set the tag of the button = the int value you want to pass. Then you can access the tag value of the button to get the int you wanted.
NSInteger is not a pointer. Try this
NSInteger tid = sender.tag;
It's working now :D.
{
NSInteger tid = [sender tag];
}
The MVC model used in Cocoa works differently. Basically, the idea is that a control (=view) such as a button only lets a function know it was pressed, not knowing what this means. The function then has to know all the dynamics and dependencies. In your case, it's the function that has to find the parameter. To accomplish that, you'll "bind" other objects to the function (= controller).
I suggest you read a few Cocoa tutorials first if you want to get ahead with iPhone programming.
There's a few good ways to do this. The two most commonly implemented would be to have the controller (who's receiving the action) know about possible senders, or having the sender itself have a method that you end up using to determine the proper behavior.
The first (my preferable way, but it's easy to argue the opposite) would be implemented like such:
#interface Controller : NSObject {
UIButton *_replyToSender;
UIButton *_replyToAll;
}
- (void)buttonClicked:(id)sender;
#end
#implementation Controller
- (void)buttonClicked:(id)sender {
if (sender == _replyToSender) {
// reply to sender...
} else if (sender == _replyToAll) {
// reply to all...
}
}
#end
The second way would be implemented in a manner such as:
typedef enum {
ReplyButtonTypeSender = 1,
ReplyButtonTypeAll,
} ReplyButtonType;
#interface Controller : NSObject {
}
- (void)buttonClicked:(id)sender;
#end
#interface MyButton : UIButton {
}
- (ReplyButtonType)typeOfReply;
#end
#implementation Controller
- (void)buttonClicked:(id)sender {
// You aren't actually assured that sender is a MyButton, so the safest thing
// to do here is to check that it is one.
if ([sender isKindOfClass:[MyButton class]]) {
switch ([sender typeOfReply]) {
case ReplyButtonTypeSender:
// reply to sender...
break;
case ReplyButtonTypeAll:
// reply to all...
break;
}
}
}
#end