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

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.

Related

Delegates in iOS

I am a newbie to iOS world, so please ignore the obvious.
I am pushing a viewController(HelpViewController) on top of another viewController(MainViewController). When a particular action happens in the HelpViewController, I would like to update a variable inside the MainViewController. I understand for this I need to use delegate.
Here is my delegate header...
#protocol ViewControllerDelegate <NSObject>
#required
- (void) switchToggled:(BOOL)status;
#end
// Protocol Definition ends here
#interface ViewDelegate : NSObject
{
// Delegate to respond back
id <ViewControllerDelegate> _delegate;
}
#property (nonatomic,strong) id delegate;
-(void)sendMessage:(BOOL)status; // Instance method
#end
and implementation...
#implementation ViewDelegate
#synthesize delegate;
-(id)init {
self = [super init];
return self;
}
-(void)sendMessage:(BOOL)status
{
[delegate switchToggled:status];
}
- (void)dealloc
{
[super dealloc];
}
#end
So Now If I want to implement Protocol ViewControllerDelegate I need to specify in MainViewController, which I do as follows --
MainViewController <ViewControllerDelegate>
and
#pragma mark - ViewControllerDelegate delegate
-(void)switchToggled:(BOOL)status{
NSLog(#"Switch Toggled(%d) Message passed to MainViewController",status);
}
My question is how do I specify Object, which delegate property needs to point to, so that it can come back to MainViewController's "switchToggled".
One way I do is by having property inside HelpViewController as follows -
MainViewController.m
HelpViewController *helpVC = [[HelpViewController alloc] init];
helpVC.mainView = self;
[self.navigationController pushViewController:helpVC animated:YES];
[helpVC release];
HelpViewController.h
#property (nonatomic) MainViewController *mainView;
HelpViewController.m
#synthesize mainView;
ViewDelegate *myDelegate = [[ViewDelegate alloc] init];
// assign delegate
myDelegate.delegate = mainView;
[myDelegate sendMessage];
[myDelegate release];
Is this correct way to implement or there is better way to achieve this or am I totally wrong.
Thanks
You should do:
// HelpViewController.h
#protocol HelpDelegate
- (void)switchToggled:(BOOL)status;
#end
// HelpViewController.m
#interface HelpViewController : UIViewController
#property (nonatomic, assign) id<HelpDelegate> delegate;
- (id)initWithDelegate:(id<HelpDelegate>)delegate
#end
#implementation HelpViewController
- (id)initWithDelegate:(id<HelpDelegate>)delegate
{
if (self = [super init])
{
self.delegate = delegate;
}
}
- (void)sendMessage:(BOOL)status
{
[self.delegate switchToggled:status];
}
// MainViewController.h
#import "HelpViewController.h"
#interface MainViewController.h : UIViewController <HelpDelegate>
// MainViewController.m
- (void)someMethod
{
HelpViewController* viewController;
viewController = [HelpViewController alloc] initWithDelegate:self];
...
}
#pragma mark - Help Delegate
- (void)switchToggled:(BOOL)status
{
...
}
Give the delegate a name that makes clear to which class it belongs.
You don't need the extra class/files for ViewDelegate/ViewControllerDelegate. Just define the delegate in header of class it belongs to: HelpViewController.n in this case.
Similar: Implement the delegate method switchToggled: in the real class MainViewController, and not in the extra/unnecessary class ViewDelegate.
The purpose of delegates is to avoid class dependencies. By including MainViewController in HelpViewController you create such a dependency. This is not necessary as I show, and is wrong design.
You were also creating a circular dependency, because MainViewController already needed HelpViewController in order to show it, and now they need each other the other way around for sending the event.
Alternatively you can make HelpViewController's delegate public, have an init without argument, and expect users to set it with helpViewController.delegate = self; or something. But this would only make sense when the delegate being set is optional (which don't seems the case here, so adding it to the init method is appropriate).
I tell you what I would have done:
1) the protocol definition is ok, but do NOT create the class ViewDelegate, so:
//ViewControllerDelegate.h
#protocol ViewControllerDelegate <NSObject>
#required
- (void) switchToggled:(BOOL)status;
#end
2) Your implementation of the delegate method in MainViewController is ok.
3) Now... the important point:
//interface
#interface HelpViewController : UIViewController //or whatever superclass..
{
id <ViewControllerDelegate> _delegate;
}
#property (nonatomic,strong) id<ViewControllerDelegate> delegate;
#end
//implementation
#implementation HelpViewController
- (void)someMethodWhichCallsTheDelegate
{
//do something
...
// call delegate
//if switchToggled: were optional then add the following
//if ([self.delegate respondToSelector:#selector(switchToggled:)]) {
[self.delegate switchToggled:status];
}
#end
4) Now you have to assign the delegate:
//MainViewController.m
HelpViewController *helpVC = [[HelpViewController alloc] init];
helpVC.delegate = self;
[self.navigationController pushViewController:helpVC animated:YES];
[helpVC release];
And that's it!
BTW: if this delegate is related only to HelpViewControllerthen add the protocol definition where you define the interface of the class, it is not necessary to create a separate header file. If instead the protocol is "global", then it can have some sense to declare it separately.

event delegation

I have two uiviewcontroller: MainViewController and SecondaryViewControlle. In MainViewController I do:
[self.view addSubView:SecondaryViewControlle.view];
The SecondaryViewController is a button by pressing the function to be performed by a MainViewController. How to do?
You'd start by defining a protocol in your SecondViewControlle.h file, something like:
#protocol SecondViewControlleDelegate
- (void) doSomething
#end
You would also need to add a "delegate" ivar to your SecondViewControlle .h file. It would be the delegate line:
#interface SecondViewControlle : UIViewController
...
...
...
#property (nonatomic, assign) id delegate; // all you need to do is add this line inside your interface declarations
...
...
...
#end
Then, when you create / instantiate your SecondaryViewControlle from your MainViewController, make certain to add the MainViewController as the delegate like so:
SecondaryViewControlle.delegate = self;
[self.view addSubView:SecondaryViewControlle.view];
Now the "delegate" of your SecondaryViewControlle view controller points back to your MainViewController.
And when the button is pressed, you can simply do something like:
- (IBAction) buttonIsPressed: (id) sender
{
[delegate doSomething];
}
Now, I need to give you some advice here.
1 ) DO NOT use the class names as object names. Instead of having an object named "SecondViewControlle", name it something different (and start it with a lower case, which is Objective-C convention), something like "moreDetailVC".
2) I've told you how to do this with a delegate pattern, but this may not be the most appropriate way to do whatever it is that you're trying to do. After all, the MainViewController object (which should be renamed mainVC to differentiate the object from the class) is not on screen or visible so maybe there's a better place to put the functionality?
Option A
It's quicker, and easier, but lacks the maintainability, since there is no contract stating that SecondaryViewController needs to bother calling anything, and self.parentViewController could be any UIViewController.
Option B
The delegate pattern; this is my preference, it's obvious what's happening, what's required, and there's a nice solid contract that states, if you want to initialise me, give me a delegate.
Option C
If SecondaryViewController has to notify multiple objects, it would be quick to use the NSNotificationCenter, but as with Option A, there's no contract, should you need to notify many objects, you would need to remember to listen for notifications on those objects - since this is not the question, I won't go into detail, it's just here for the information
Option A
Within MainViewController.m, do something like so:
SecondaryViewController *viewcontroller = [[SecondaryViewController alloc] initWithNibName:#"SecondaryView" bundle:nil];
[self addChildViewController:viewcontroller];
//set viewcontroller.view frame
[self.view addSubview:viewcontroller.view];
[viewcontroller didMoveToParentViewController:self];
Inside MainViewController.h
-(void) performButtonClickAction;
Inside MainViewController.m:
-(void) performButtonClickAction {
//Do something constructive
}
and then inside the SecondaryViewController.m:
-(IBAction) buttonPressed:(id) sender {
[self.parentViewController performButtonClickAction];
}
Option B
Inside SecondaryViewController.h
#protocol SecondaryViewControllerDelegate <NSObject>
-(void) eventAFromViewController:(UIViewController *) viewController;
-(void) eventBFromViewController:(UIViewController *) viewController;
#end
#interface SecondaryViewController : UIViewController {
id<SecondaryViewControllerDelegate> delegate;
}
#property (assign, nonatomic) id<SecondaryViewControllerDelegate> delegate;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(id<SecondaryViewControllerDelegate>) theDelegate;
#end
Inside SecondaryViewController.m
#synthesize delegate = _delegate;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil delegate:(id<SecondaryViewControllerDelegate>) theDelegate
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.delegate = theDelegate;
}
return self;
}
-(IBAction) buttonPressed:(id) sender {
if( self.delegate != nil ) {
[_delegate eventAFromViewController:self];
}
else {
//No delegate
}
}

MKMapViewDelegate derived class and delegate assignment

This is probably more of an objective-c question over iOS but I've seen some example code similar to the following that I'd like to better understand.
#interface MyMapView : MKMapView <MKMapViewDelegate> {
// ivars specific to derived class
}
#property(nonatomic,assign) id<MKMapViewDelegate> delegate;
#end
#implementation MyMapView
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// initialize the ivars specific to this class
// Q1: Why invoke super on this delegate that's also a property of this class?
super.delegate = self;
zoomLevel = self.visibleMapRect.size.width * self.visibleMapRect.size.height;
}
return self;
}
#pragma mark - MKMapViewDelegate methods
// Q2: Why intercept these callbacks, only to invoke the delegate?
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
if( [delegate respondsToSelector:#selector(mapView:regionWillChangeAnimated:)] )
{
[delegate mapView:mapView regionWillChangeAnimated:animated];
}
}
#end
My two questions are:
1. Why would one invoke the super.delegate and also only declare the 'delegate' as a property?
2. Why intercept all of the delegate calls only to forward them back to the delegate?
I appreciate any insights.
Apple's documentation explicitly states that you should avoid subclass MKMapView:
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html#//apple_ref/doc/uid/TP40008205
Although you should not subclass the MKMapView class itself, you can
get information about the map view’s behavior by providing a delegate
object.
So i guess this delegate "forward" pattern is used to not break things.
I use a little different approach to subclass MKMapView. To minimize breakage i use two classes. One that subclass MKMapView and just override the init/dealloc method and assign/release the delegate property to a instance of the other class. The other class is a subclass of NSObject that implements the MKMapViewDelegate protocol and will be the one that does the real work.
MyMapView.h
#interface MyMapView : MKMapView
#end
MyMapView.m
// private map delegate class
#interface MapDelegate : NSObject <MKMapViewDelegate>
// instance is not alive longer then MKMapView so use assign to also solve
// problem with circular retain
#property(nonatomic, assign) MKMapView *mapView;
#end
#implementation MapDelegate
#synthesize mapView;
- (id)initWithMapView:(ReportsMapView *)aMapView {
self = [super init];
if (self == nil) {
return nil;
}
self.mapView = aMapView;
return self;
}
// MKMapViewDelegate methods and other stuff goes here
#end
#implementation MyMapView
- (id)init {
self = [super init];
if (self == nil) {
return nil;
}
// delegate is a assign property
self.delegate = [[MapDelegate alloc] initWithMapView:self];
return self;
}
- (void)dealloc {
((MapDelegate *)self.delegate).mapView = nil;
[self.delegate release];
self.delegate = nil;
[super dealloc];
}
#end
The mapView property for MapDelegate class is not strictly needed but is probably useful if want to do things to the map view that that is not a result of some MKMapViewDelegate method call, timers etc.
Why would one invoke the super.delegate and also only declare the 'delegate' as a property?
Ans. As you are making a custom mapview it is important to call the delegates too.We are invoking the super class delegate to send control from the custom Mapview.
Why intercept all of the delegate calls only to forward them back to the delegate?
Ans.At that line of code we are sending back the control to that delegate method declared in super class to do some useful thing.
Hope it will solve the query.

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

How to add properties/methods to UIButton

I need to add two additional properties (NSString * and NSMutableArray *) along with three extra methods to UIButton. I also want to reference the new objects using a supertype if that is possible. I do not necessarily want to subclass (as I read that it is tricky and not recommended), but I am quite new to Objective-C and iOS development and don't know what else to do.
I tried to subclass UIButton with my subclass implementing a formal protocol in the following way:
#interface Button : UIButton <MyProtocol> ...
However, I found out this doesn't work like I thought it would, as buttonWithType: returns an object from a subclass. What else can I do to achieve the desired result?
-- EDIT:
Ok, my current code is like this:
#interface Button : UIButton <SteapeObject> {
ActionQueue * actions;
Meta meta;
}
#property (nonatomic, retain) ActionQueue * actions;
#property (nonatomic) Meta meta;
- (id) initWithFrame:(CGRect)frame;
...
And the implementation:
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
NSLog (#"finally");
}
return self;
}
An still doesn't work. It seems that when I invoke:
Button * button = [Button buttonWithType: UIButtonTypeRoundedRect];
NSLog (#"%#", [button description]);
I should get two 'finally' strings and two descriptions in the log. However, I only get the two description strings:
[Session started at 2011-02-24 09:47:14 +0100.]
2011-02-24 09:47:15.431 IphoneClient3[702:207] <UIRoundedRectButton: 0x5f47690; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x5f47240>>
2011-02-24 09:47:15.461 IphoneClient3[702:207] <UIRoundedRectButton: 0x6a0f000; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x6a344b0>>
And you can see that the type is still UIRoundedRectButton, but the buttons do not respond to my added methods. Actually, since my overriden initWithFrame doesn't get called, that is to be expected. Perhaps I should default to implementing a custom control...
As far as I know, the doc does not say subclassing of UIButton is not recommended.
I have done it multiple times to add custom properties — without problems.
The only thing to do is to create the button using:
[Button buttonWithType:UIButtonTypeCustom]; // won't work for other button types though
Use a Category.
#interface UIButton (MyButtonCategory)
- (void) myMethod;
#end
#implementation UIButton (MyButtonCategory)
- (void) myMethod
{
NSLog(#"Called myMethod!");
}
#end
[EDIT]
Alternatively, if I finally understand you, you can do this.
#interface MyButton : UIButton
- (id) initWithFrame:(CGRect)rect;
#end
#implementation MyButton
- (id) initWithFrame:(CGRect)rect
{
if ((self = [super initWithFrame:rect])){
// Do your init in here
}
return self;
}
#end
Then calling
MyButton *btn = [MyButton buttonWithType:UIButtonTypeRoundedRect];
Should get you what you want. buttonWithType should call initWithFrame on your subclass.
I found that it is not possible to accomplish that task with the current implementation of the SDK.
Categories might help. Implement like this:
//In the UIButtonMyExtras.h file
#interface UIButton(MyExtras)
//extras
#end
//In the UIButtonMyExtras.m file
#implementation UIButton(MyExtras)
//extra implementation
#end
This adds these extras to every UIButton in your project.