Call a method from UIButton subclass #implementation - iphone

I work on a project for iPhone iOS 4 with Xcode 4.
I have subclassed a UIButton so that it intercepts single and double tap.
This is the final part of #implementation of the UIButton subclass, two instance methods where the taps are "recorded";
- (void) handleTap: (UITapGestureRecognizer *) sender {
NSLog(#"single tap");
}
- (void) handleDoubleTap :(UITapGestureRecognizer *) sender {
NSLog(#"double tap");
}
A button instance is created in nib and all works OK: it intercepts single tap and double tap and output the NSLog.
Now the question: I have in my ViewController two methods (resetAllFields and populateAllFields) and I need that single tap execute resetAllFields and double tap execute populateAllFields.
How can I do? Where do I put the call?
Thank you.

If you want to handle the behavior in the ViewController the typical solution is to add a #protocol in your custom button class that defines methods for handling the single and double taps.
i.e. in your CustomButton.h
#protocol CustomButtonDelegate <NSObject>
- (void)button:(CustomButton *)button tappedWithCount:(int)count;
#end
You then have a delegate that implements this protocol in your custom button class and call those methods on the delegate when your taps are detected.
i.e. in your CustomButton.h
id <CustomButtonDelegate> _delegate;
in your methods in the implementation:
- (void) handleTap: (UITapGestureRecognizer *) sender {
NSLog(#"single tap");
[self.delegate button:self tappedWithCount:1];
}
- (void) handleDoubleTap :(UITapGestureRecognizer *) sender {
NSLog(#"double tap");
[self.delegate button:self tappedWithCount:2];
}
Your View Controller than implements the protocol methods and sets itself as the custom button's delegate.
ie. in your ViewControllers implementation
- (void)button:(CustomButton *)button tappedWithCount:(int)count {
if (count == 1) {
[self resetAllFields];
} else if (count == 2) {
[self populateAllFields];
}
}
Since you are using Interface Builder to set the custom button you can assign your view controller as a delegate in there or in the ViewDidLoad.

Related

Delegate method in the set delegate object not responding

I have a somewhat similar concern with this question a custom delegate method inside didSelectRowAtIndexPath.
However, in my case before getting to the delegate object which is a UIViewController named BarCodeViewController, I should first pass by 2 view controllers from the initial view controller which is the CardViewController which is a table view controller. I'm setting the delegate object for my custom delegate through this:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
CardDetailsViewController *details = [self.storyboard instantiateViewControllerWithIdentifier:#"cardDetails"];
Card *selectedCard = [self.myWallet objectAtIndex:indexPath.row]; // I want this selected card to be accessible until the user clicks another card or during end of program.
[self.navigationController pushViewController:details animated:YES];
[self.delegate cardWalletViewController:self withCurrentCard:selectedCard];
[self setDelegate:self.barCodeVC]; // barCodeVC is declared in CardWalletViewController.h as #property (nonatomic, strong) BarCodeViewController *barCodeVC;
if (self.delegate) {
NSLog(#"delegate is not nil");
}
}
and this is how I instantiate the view controller which I set as the delegate object
- (void)viewDidLoad
{
[self setBarCodeVC:[self.storyboard instantiateViewControllerWithIdentifier:#"myBarcodeVC"]];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
And in my delegate object, which is the BarCodeViewController I implement the delegate method
#import "CardWalletViewController.h"
#interface BarCodeViewController () <CardWalletDelegate>
#end
#implementation
- (void)cardWalletViewController:(CardWalletViewController *)sender withCurrentCard:(Card *)currentCard
{
Card *myCurrentCard = currentCard;
NSLog(#"This is my current card: %#", myCurrentCard);
}
#end
I think I am able to set my delegate object, but then the delegate method is not being implemented for I don't see in the console the NSLog(#"this is my current......"); when I reach the BarCodeViewController.
Advice please.
That's not a standard use for delegate and it's hard to tell what you really want to happen. but, it looks like your code...
[self.delegate cardWalletViewController:self withCurrentCard:selectedCard];
[self setDelegate:self.barCodeVC];
Is making the call on whatever the "old" delegate is (before setting it to barCodeVC). Are you really trying to make the call on the "new" delegate? Should it be...
[self setDelegate:self.barCodeVC];
[self.delegate cardWalletViewController:self withCurrentCard:selectedCard];
EDIT
What I am saying is that you are sending a message to the delegate in this line...
[self.delegate cardWalletViewController:self withCurrentCard:selectedCard];
and THEN you are setting the delegate to barCodeVC
[self setDelegate:self.barCodeVC];
So, the message is actually being sent to whatever the delegate was set to before it was set to barCodeVC (another view controller, or nil, or...). Maybe that's what you want to happen, but it looks suspicious.

call method from subclass of UIImageview

I have two .m files. The first is the main code, The second is a subclass of UIImageView so that i can detect touches.
In the main .m file I have added a progress bar and a customimageview both subviews of a scrollview.
What I need is that when a user touches the customimageview that the progress bar moves up and a double tap decreases the [Note: the customimageview has to have its touches recognised in the second .m because of them being in a subview of a scrollview and other controls are having to be handled]
In the main .m file I have a two methods:
- (void)pumpsingletap {
progbm.progress +=0.1;
}
- (void)pumpdoubletap {
progbm.progress -=0.1;
}
then in the subclassed uiimageview i have:
//inside touches method
if ([touch view].tag == 555) {
NSLog(#"pump touched");
switch ([allTouches count]) {
case 1: {
switch ([touch tapCount]) {
//---single tap---
case 1: {
NSLog(#"single pump touch");
[self performSelector:#selector(pumpsingletap) withObject:nil afterDelay:.4];
} break;
//---double tap---
case 2: {
NSLog(#"double pump touch");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(pumpsingletap) object:nil];
[self performSelector:#selector(pumpdoubletap) withObject:nil afterDelay:.4];
} break;
}
}
}
}
So the NSlog's appear so the touch recognition isn't an issue. But the performSelector falls over. As the customimageview pumpsingletap doesnt work.
So how do i call the method in the subclass.
//update//
so I have added in the following code, in my subclass
mainMethod* callingMethod = [[mainMethod alloc] init];
[callingMethod performSelector:#selector(pumpsingletap) withObject:nil afterDelay:.4];
then in my main method for pumpsingletap i changed it to:
- (void)pumpsingletap {
NSLog(#"single pump method called");
progbm.progress +=0.1;
}
The NSLog for single pump method called appeared but the progress bar progbm - didn't move. so i have solved my calling issue - just need to now work out why the progress bar isnt moving!!
If I'm following the code correctly, you are performing the selector on self, but self is your derived UIImageView class (so you probably crash at that point?). You need to perform selector on the class in your main file. Pass a reference to that to your derived class. Alternately, you could create a delegate, implement it in your main class, pass your main class to the UIImageView and then call through the delegate (there are even other ways to do it (key-value observation), but one of these should work).
I'm not sure if this is the problem, but you don't need brackets around case statements. Also I don't get why you would make a switch within a switch. Just make an if-elseif-else statement, it'll probably be easier to understand.
Other than this, from what I understand, you have a view controller with both a progress bar and a customimageview as properties, and you have methods that should be called in response to certain actions, (tapping or double tapping the customimageview) but they're in the view controller. The usual way to solve this is by using the target action mechanism. UIControls implement the target action mechanism by encapsulating target-action pairs and storing them in a dictionary, keyed by the event type (UIControlEvent). Here's a slightly simpler version.
In the .h file for your subclass of UIImageView, before the #interface write this:
typedef enum {
ControlEventTap = 0,
ControlEventDoubleTap
} ControlEvent;
Then in the .m file add this before the #implementation:
#interface TargetActionPair : NSObject {
id target;
SEL action;
}
#property (nonatomic, assign) id target;
#property (nonatomic, assign) SEL action;
#end
#implementation TargetActionPair
#synthesize target, action;
#end
Then, add an NSMutableArray instance variable, (but not a property) and a - (void)setTarget:(id)t action:(SEL)a forEvent:(ControlEvent)e method to your customimageview implementation.
The method should look like this:
- (void)setTarget:(id)t action:(SEL)a forEvent:(ControlEvent)e {
TargetActionPair *tar_act = [[TargetActionPair alloc] init];
tar_act.target = t;
tar_act.action = a;
// actionsArray is the mutable array instance variable and must be allocated and set in the init method for customimageview.
[actionsArray replaceObjectAtIndex:(NSUInteger)e withObject:tar_act];
[tar_act release];
}
Then you can replace your touch handling code with:
if ([touch view].tag == 555) {
NSUInteger tapcount = [touch tapCount];
if (([alltouches count] == 1) && (tapcount <= [actionsArray count])) {
TargetActionPair *tar_act = [actionsArray objectAtIndex:tapcount-1];
[tar_act.target performSelector:tar_act.action withObject:nil afterDelay:.4];
if (tapcount == 2) {
TargetActionPair *tar_act2 = [actionsArray objectAtIndex:tapcount-2];
[NSObject cancelPreviousPerformRequestsWithTarget:tar_act2.target selector:tar_act2.action object:nil];
}
}
}
With this code you simply set the target and action for each control event in the viewDidLoad method of the view controller that contains the customimageview. So the calls would look like this:
[self.customimageview setTarget:self action:#selector(pumpsingletap) forEvent:ControlEventTap];
[self.customimageview setTarget:self action:#selector(pumpdoubletap) forEvent:ControlEventDoubleTap];
DON'T FORGET to release the actionsArray in your dealloc method and to be very careful about releasing the view controller since the customimageview doesn't retain it.
I hope this helps, best of luck on your app.
In the end I solved the issue by using NSNotificationCenter

-(void)textFieldDidBeginEditing:(UITextField *)sender.. this function is not called when i select textfield?

I m using -(void)textFieldDidBeginEditing:(UITextField *)sender this function in my application. this is not called when i select the textfield.
here's the code...
-(void)textFieldDidBeginEditing:(UITextField *)sender{
if([sender isEqual:txtName])//txtName is the IBOutlet of the UITextField
{
NSLog(#"Name");
}
else{
NSLog(#"NO_Name");
}
}
Did you set delegate of UITextField's instance to current view controller like this:
textField.delegate = self; (self means the instance where callback textFieldDidBeginEditing is overridden)
Make sure to complete 2 simple steps
1 - Implement the delegate UITextFieldDelegate
#interface yourViewController : UIViewController <UITextFieldDelegate>
2 - Set the delegate
yourTextField.delegate = self
If you have many text fields in your view, you can set delegate for all of your text fields like this
for (id subView in self.view.subviews)
{
if ([subView isKindOfClass:[UITextField class]]) {
[subView setDelegate:self];
}
}
You must include UITextFieldDelegate in .h file
#interface yourViewController : UIViewController <UITextFieldDelegate>
You must include UITextFieldDelegate in your .h file, and also add YourTextField.delegate = self

UISearchBar and event fired when 'X' element is tapped

On the UISearchBar, there's an X element that allows you to clear all of the contents at once. Is there a way to get notified when this happens?
UISearchBarDelegate::searchBarCancelButtonClicked is fired only when the "Cancel" button is tapped.
The UISearchBar doesn't have a delegate method for this event. You can nearly get what you want by implementing the textDidChange: method of the callback delegate and checking for an empty string.
I don't recommend it, but there is another possible way. The UISearchBar is composed of a UITextField, which does have a delegate method that is called when the user taps the clear button (textFieldShouldClear:). You can get the UITextField by traversing the UISearchBar's child views:
(this is in the context of a derived UISearchBar class)
- (UIView*) textField
{
for (UIView* v in self.subviews)
{
if ( [v isKindOfClass: [UITextField class]] )
return v;
}
return nil;
}
from here, you could re-assign the UITextField delegate to your own implementation, taking care to forward delegate calls to the old delegate. This way you could intercept textFieldShouldClear:. Or if it turns out the UISearchBar is the delegate for the UITextField it contains you could swizzle the call to textFieldShouldClear:... Not ideal, clearly, but technically feasible.
I had the same issue and I solved this issue by using this function.
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
// This method has been called when u enter some text on search or Cancel the search.
if([searchText isEqualToString:#""] || searchText==nil) {
// Nothing to search, empty result.
[UIView animateWithDuration:0.2 animations:^ {
//Reposition search bar
[_searchBar setFrame:CGRectMake(230, 26, 43, 44)];
[_searchBar setNeedsLayout];
}];
}
}
Here is an answer from a previous question, this should do exactly what you want. UISearchbar clearButton forces the keyboard to appear
Here is "Method Swizzling" solution.
Create a new Category of UISearchBar. This category create a new method and swizzle method between -(BOOL)textFieldShouldClear:(UITextField *)textField; and -(BOOL)jbm_textFieldShouldClear:(UITextField *)textField in runtime.
Customize a new Protocol of UISearchBarDelegate in order to add a new method - (void)searchBarClearButtonClicked:(id)sender;
UISearchBar+JMBTextFieldControl.h
#protocol UISearchBarWithClearButtonDelegate <UISearchBarDelegate>
#optional
- (void)searchBarClearButtonClicked:(id)sender;
#end
#interface UISearchBar (JMBTextFieldControl)
#end
UISearchBar+JMBTextFieldControl.m
#import "UISearchBar+JMBTextFieldControl.h"
#import <objc/runtime.h>
#implementation NSObject (Swizzling)
+ (void)brc_swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
Method origMethod = class_getInstanceMethod(self, origSelector);
Method newMethod = class_getInstanceMethod(self, newSelector);
if(class_addMethod(self, origSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(self, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
#end
#implementation UISearchBar (JMBTextFieldControl)
+ (void)load {
[self brc_swizzleMethod:#selector(textFieldShouldClear:) withMethod:#selector(jbm_textFieldShouldClear:)];
}
- (id<UISearchBarWithClearButtonDelegate>)jbm_customDelegate {
if( [[self delegate] conformsToProtocol:#protocol(UISearchBarWithClearButtonDelegate)] )
return (id<UISearchBarWithClearButtonDelegate>)[self delegate];
else
return nil;
}
- (BOOL)jbm_textFieldShouldClear:(UITextField *)textField
{
if ( [[self jbm_customDelegate] respondsToSelector:#selector(searchBarClearButtonClicked:)] )
[[self jbm_customDelegate] searchBarClearButtonClicked:self];
return [self jbm_textFieldShouldClear:textField];
}
#end
Reference
Dave DeLong -
How to add a method to an existing protocol in Cocoa?
Nikolay Vlasov - CCBottomRefreshControl

How to implement target-action-mechanism for custom control?

I'm going to write my own custom control that is very different from UIButton. It's so much different that I decided to write it from scratch. So all I subclass is UIControl.
When my control is touched up inside, then I want to fire a message in means of target-action. The user of that class may instantiate it and then add some targets and actions for this event.
i.e. imagine I would call internally a method -fireTargetsForTouchUpEvent. How could I maintain this target-action-mechanism in my class? Do I have to add all targets and actions to my own array and then just call selectors (the actions) on the target objects in a for-loop? Or is there a more intelligent way to do it?
I imagine to provide some methods for adding targets and actions for some events like that touch up event (I raise that manually by calling a internal method when that happens). Any idea?
I just want to clarify what #Felixyz said because it wasn't clear to me at first.
If you are subclassing UIControl, even if you are going to have a custom event, you don't have to keep track of your own targets/actions. The functionality is already there, all you have to do is call the code below in your subclass to trigger the event:
[self sendActionsForControlEvents:UIControlEventValueChanged];
Then in the view or view controller that instantiates your custom UIControl, just do
[customControl addTarget:self action:#selector(whatever) forControlEvents:UIControlEventValueChanged];
For custom event, just define your own enum (for example, UIControlEventValueChanged is equal to 1 << 12). Just make sure it is within the permitted range defined by UIControlEventApplicationReserved
You have the right idea. Here is how I would do it:
#interface TargetActionPair : NSObject
{
id target;
SEL action;
}
#property (assign) id target;
#property (assign) SEL action;
+ (TargetActionPair *)pairWithTarget:(id)aTarget andAction:(SEL)selector;
- (void)fire;
#end
#implementation TargetActionPair
#synthesize target;
#synthesize action;
+ (TargetActionPair *)pairWithTarget:(id)aTarget andAction:(SEL)anAction
{
TargetActionPair * newSelf = [[self alloc] init];
[newSelf setTarget:aTarget];
[newSelf setAction:anAction];
return [newSelf autorelease];
}
- (void)fire
{
[target performSelector:action];
}
#end
With that class in place, storing your target/action pairs is pretty straightforward:
MyCustomControl.h:
#import "TargetActionPair.h"
#interface MyCustomControl : UIControl
{
NSMutableArray * touchUpEventHandlers;
}
- (id)init;
- (void)dealloc;
- (void)addHandlerForTouchUp:(TargetActionPair *)handler;
#end
MyCustomControl.m:
#import "TargetActionPair.h"
#implementation MyCustomControl
- (id)init
{
if ((self = [super init]) == nil) { return nil; }
touchUpEventHandlers = [[NSMutableArray alloc] initWithCapacity:0];
return self;
}
- (void)dealloc
{
[touchUpEventHandlers release];
}
- (void)addHandlerForTouchUp:(TargetActionPair *)handler
{
[touchUpEventHandlers addObject:handler];
}
- (void) fireTargetsForTouchUpEvent
{
[touchUpEventHandlers makeObjectsPerformSelector:#selector(fire)];
}
#end
After that, setting up the control would be done as follows:
[instanceOfMyControl addHandlerForTouchUp:
[TargetActionPair pairWithTarget:someController
andAction:#selector(touchUpEvent)];
Since you're planning to subclass UIControl, you can just use
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
Using this, any class can register itself as a target for any events it wants to on your custom controller.