BuySellAds.com

Until Dec 3rd, 44% off all Manning books, including Barcodes with iOS! Promo code: mobicftw
Our DNA is written in Objective-C
Jump

UIImageView + Touch Handling = UIButton

gdscei asks:

“Hello, how can i make an image view work as a button? I want it to be assigned to the ‘reload’ action of WebView.
Can someone give me instructions how to do this?”

Often people start out constructing their UI visually, starting with this line of reasoning “I want to show an image. Ah, UIImageView”. So they create the visual appearance of their UI either by clicking it together in Interface Builder or – if slightly more advanced in their coding skills – creating those image views in code.

So if we create a new view-based project in XCode, we could modify the viewDidLoad of the main view controller like this to show the image:

- (void)viewDidLoad {
    [super viewDidLoad];
 
	UIImageView *imageView = [[UIImageView alloc] initWithFrame:
		CGRectMake(100.0, 100.0, 57.0, 57.0)];
	imageView.image = [UIImage imageNamed:@"Icon.png"];
	[self.view addSubview:imageView];
	[imageView release];
}

The next logical step in reasoning right after displaying the icon is now to get touch handling somehow. And that’s where you will get stuck because UIImageView is a dead end when it comes to reacting to the user’s finger.

Sure, if you consider yourself extra smart and that you won’t be stumped by such an inconvenient “cul de sac” then you will proceed to subclass UIImageView and adding touch handling via the touchesBegan, touchesMoved, touchesCancelled and touchesEnded method. And don’t go telling me that you would never make such a mistake. I know, I did and I’ve seen smarter programmers than the both of us proceed to painstakingly code their own touch handling for UIImageViews.

This post is NOT about doing that. I cannot prevent you from scratching your left ear with your right index finger, but I CAN show a smarter way to get your own interactive custom buttons.

I already gave away half of the solution: CUSTOM BUTTONS. Apple made it confusing to beginners by having the default style for a button being a rounded rectangle with text inside. That’s why there is such a strong trend to using UIImageViews because beginners cannot imagine that their beautifully crafted button images would fit into this rounded container. But in fact the rounded rectangle style is only one of several possibilities.

UIButtonType can have these values:

  • UIButtonTypeCustom,
  • UIButtonTypeRoundedRect
  • UIButtonTypeDetailDisclosure
  • UIButtonTypeInfoLight
  • UIButtonTypeInfoDark
  • UIButtonTypeContactAdd

The very first on UIButtonTypeCustom will be our choice for making our own custom button. For all the other button types Apple provides the drawing, for the custom one you have to bring your own, preferably in the form of UIImages for the various states.

Let’s have a brief look at the inheritance of UIImageView versus UIButton. This kinship was not obvious to me, I only discovered it by accident when I also was in the situation that I wanted to touch-enable some subviews.

UIImageView inherits from UIView : UIResponder : NSObject
UIButton inherits from UIControl : UIView : UIResponder : NSObject

So we can see that up to UIView both have the same set of methods and behaviors. An image view is just a UIView where an image from a property is being drawn in the view’s area, probably inside the drawRect. A button gains an additional level of inheritance: UIControl. If you read up on UIControl in the documentation it tells you that UIControl adds mechanisms to “convey user intent to the application”. It adds the target-action methodology to UIView where you can add targets and methods as actions that get executed when certain things happen. Like for example a touch is lifted inside a button or the value of a slider has changed.

But UIImageView and UIButton are views! Both have the genes of UIView inside of them and cannot deny their pedigree.

Knowing the above mentioned facts, why would you want to painstakingly code your own touch handling routines if you get them for free when choosing UIButton to display your icon image instead?

We can replace the above mentioned code with this:

- (void)viewDidLoad {
    [super viewDidLoad];
 
    UIButton *imageButton = [UIButton buttonWithType:UIButtonTypeCustom];
    imageButton.frame = CGRectMake(100.0, 100.0, 57.0, 57.0);
    [imageButton setImage:[UIImage imageNamed:@"Icon.png"] forState:UIControlStateNormal];
    [self.view addSubview:imageButton];
}

Note that for buttons you generally don’t do alloc-initWithFrame, but use the factory method buttonWithType. Since this gives us an autoreleased button we don’t need to manually release it after addSubview.

If you do that you get exactly the same result, with a minor difference. If you tap the icon it darkens automatically to show the touch. That’s because UIButton expects to display something different when the button is touched. You can set a specific image for the highlighted state, if you don’t then UIButton takes the normal state image and darkens it automatically.

To disable this behavior change the property or set an image for the highlighted state:

imageButton.adjustsImageWhenHighlighted = NO;
// or
[imageButton setImage:[UIImage imageNamed:@"Icon_highlighted.png"]
	forState:UIControlStateHighlighted];

Possible control states are:

enum {
   UIControlStateNormal               = 0,
   UIControlStateHighlighted          = 1 << 0,
   UIControlStateDisabled             = 1 << 1,
   UIControlStateSelected             = 1 << 2,
   UIControlStateApplication          = 0x00FF0000,
   UIControlStateReserved             = 0xFF000000
};

Therefore control state is more than a single value, it actually can also be a combination of several bits. The normal state is 0, the highlighted state is 1, disabled 2 and selected 4. You can specify any combination thereof by using the bitwise or operator |. For example to set an image for selected OR highlighted you use UIControlStateSelected|UIControlStateHighlighted. This possibility is not clear if you look at the inspector for a button in Interface Builder. That’s why I prefer to do my buttons in code.

To clarify the states: A button at rest is “normal”. As long as you put your finger down on it, it’s “highlighted”. If you lift again, it returns to “normal”. If you set it’s enabled property to NO, then it’s “disabled”. If you set it’s selected property to YES, then it’s “selected”.

Cocoa Cracks NOTE that UIControlStateApplication actually shows us a range (16 bits) that we can use ourselves to define extra special states for our own controls. And UIControlStateReserved is a range (16 bits) that is reserved for Apple-internal use.

Ok, so much for states and looks. Adding targets and actions for the various possible interactions with a button is easy again:

[imageButton addTarget:self action:@selector(buttonPushed:)
	  forControlEvents:UIControlEventTouchUpInside];

We also have to implement the “action” which is just a method. The target is the class instance where you implement the method, the action is a selector, i.e. the “fingerprint” of the method.

- (void) buttonPushed:(id)sender
{
	NSLog(@"It works!");
}

Note that a pointer to your button instance will be passed via the sender parameter. This can sometimes be useful, for example if you want to use a single method to respond to multiple buttons. In this case you would use the tag of the passed sender to discern which button the call was coming from.

This method of making images respond to touches works with very little extra tying. I’m using it in DTCalendarView for the individual days as well as for a full screen tap recognition in DTSplashExtender. Best of all: it saves you lot of extra code or even having to subclass UIImageView.


Categories: Q&A

%d bloggers like this: