Touch event in UIImageView - iphone

How can I determine what UIImageView I touch in the screen? Like for example, I added 10x10 tiled UIImageView in a UIView. Now in touchesBegan, how can I know the image I touch in screen? Should I use hitTest method in this implementation?
Thanks.

I generally find the easiest way is to subclass UIImageView and add my own touch event handlers.
In fact what I have often done is create one subclass of UIImageView who's sole purpose is to allow another class to act as next responder. That way I don't have to subclass it for every situation. That's only worth doing if you need a number of these views and you weren't subclassing them anyway.

this question was asked many times here. try to use search next time. I usually use UIButtons with custom style to do what you want. such a variant gives you standard methods to catch touches without subclassing.

To follow up on the UIButton suggestion, here's a snippet of what I have done for a UIButton that is rendered with a UIImage.
This was for tracking the specific UIButton image type that got touched within a UITableViewCell, and it passes along the row and section in which this UIButton was placed:
UIButton *_infoButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[_infoButton setImage:[UIImage imageNamed:#"Info.png"] forState:UIControlStateNormal];
[_infoButton setTitle:[NSString stringWithFormat:#"%d:%d", indexPath.section, indexPath.row] forState:UIControlStateReserved];
[_infoButton addTarget:self action:#selector(touchedDetailedInfoButton:) forControlEvents:UIControlEventTouchDown];
// ...
[ _infoButton release];
And here's the associated method for recovering the title and doing something interesting with it:
- (void) touchedDetailedInfoButton:(NSString *)title {
unsigned int _section, _row;
const char * _indexPathCharPtr = [title cStringUsingEncoding:NSUTF8StringEncoding];
sscanf(_indexPathCharPtr, "%d:%d", &_section, &_row);
NSUInteger _path[2] = {_section, _row};
NSIndexPath *_touchedIndexPath = [[NSIndexPath alloc] initWithIndexes:_path length:2];
[self doSomethingInterestingWithIndexPath:_touchedIndexPath];
[_touchedIndexPath release];
}
There's probably an easier way to go about this, so hopefully someone else comes along and offers an alternative (other than subclassing UIImageView, which is a perfectly valid option, as well).

Related

Set UIImageView background color upon user intervention

I am developing an application in which I have a horizontal scroller.
The scroller area comprise of different colors [which are nothing but UIImageView of colors].
Above the scroller area is a canvas [which is also a UIImageView].
Now, what I want is that when I select any color in the scroller area, it should set on the canvas.
It is pretty much like a color picker but I am already providing solid color options to be selected by the user.
How should I go about it ? Any sample Code to get me started or my mind kicking would be awesome ?
Thanx a ton
To implement it easily.
Use UIView not UIImageView
Set UIView backgroundColor and place it on the scroll
Set UITapGestureRecognizer on views,Refer here
when a purticular view is touched you have its instance of view which
is touched
get the background color of the view and set it in the scroller
Can you take UIButtons instead of UIImageViews inside your scrollView to show colours to be picked.
This will make the implementation easier. User will select any colour button and you will receive a callback in buttons action selector method. Then based on button tag or any property you can set the colour to your canvas.
In current implementation it will be tricky as you need to know the exact content offset where the tap is done to make out which UIImageView was pressed.
coding steps:
In your scroll view add UIButtons instead of UIImageView like:
UIButton* button = [[UIButton alloc] initWithFrame:someRect];
//put some tag to button
button.tag = someInt;
//add selector to each button to get callback
[view addTarget:self action:#selector(btnSelected:) forControlEvents:UIControlEventTouchUpInside];
[scrollView addSubview:button];
In the btnSelected method put this code:
-(IBAction)btnSelected:(id)sender;
{
UIButton* button = (UIButton*) sender;
if (button.tag == someInt) {
//do something like
canvas.backgroundColor = button.backgroundColor;
}
}
Instead of using UIImageViews or UIViews as suggested, I would put custom UIButtons for picking colors.
UIButton * colorButton;
//You will probably do these inside a for loop
colorButton = [UIButton buttonWithType:UIButtonTypeCustom];
[colorButton setFrame:CGRectMake(X, Y, buttonSize, buttonSize)];
[colorButton setTitle:#"" forState:UIControlStateNormal];
[colorButton setBackgroundColor:SOME_COLOR];
//Border visualization would be good if you are putting buttons side-by-side
[colorButton.layer setBorderWidth:1.0];
[colorButton.layer setBorderColor:[[UIColor darkGrayColor] CGColor]];
//You can use this tag to identify different buttons later
[colorButton setTag:INDEX+SOME_OFFSET]; //use loop index with some offset
[colorButton addTarget:self action:#selector(colorButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
-(void) colorButtonPressed:(UIButton *) sender
{
UIColor *selectedColor = sender.backgroundColor; //Or identify with sender.tag
[yourCanvas doSmtOnCanvas:selectedColor];
}
Initialize UIImageView then add gesture recognizer to all imageViews by calling this method.
- (void) setupImageView : (UIImageView *) imageView{
[imageView setUserInteractionEnabled:YES];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(changeUIImageViewBackgroundColor:)];
tapGestureRecognizer.numberOfTapsRequired = 1;
[imageView addGestureRecognizer:tapGestureRecognizer];
}
Now change color of canvasView within the selector by following way:
- (void) changeUIImageViewBackgroundColor : (UITapGestureRecognizer *) recognizer{
UIImageView *imageView = (UIImageView *)[recognizer view];
[canvasView setBackgroundColor:[imageView backgroundColor]];
}
You can put a tap gesture in your scrollview and whenever a tap is made in the scroll check whether the tap is on the imageview. (You will get whether the tap point in on imageview or not) and according you can change the image.
Second alternative is to take a custom button and handle on its click.
Hope it helps

can I add a uiprogressbar to a uibutton

I want to know if it is possible to subclass UIButton someway to add another component to it such as a preloader or progress indicator.
In pseudo code what I have in mind is something like the following.
UIButtonWithProgress *button =[[UIButtonWithProgress alloc]init]
[button.progressbar setProgress:50%]
In iOS the class for a progress bar is UIProgressView. If you really wanted to add a UIButton to a UIProgressBar (as in the question title). Then you can use the UIView method addSubView: For example:
UIButtonWithProgress* button = [[UIButtonWithProgress alloc] init];
UIProgressView* progressView = [[UIProgressView alloc] init];
[button addSubview:progressView];
progressView.progress = 0.5;
I think what you really want to do is create your own UIControl (so you should subclass that instead). You can override the method drawRect: to use quartz to draw your own progress bar animation, and the touchesBegan:withEvent: to detect the user tapping on your custom control, as well as adding any other methods you wish to control the state of the control (such as the progress level).
Note: In my code example I also give an example of correctly setting the progress of the UIProgressBar, just in case you were unclear.

UITabbarController not drawing a view properly

I have a uitabbarcontroller which has one UIViewController added to it. (I reduced to smallest case possible). This viewcontroller calls up a UIView which draws a UIButton via:
- (void)drawRect:(CGRect)rect
{
CGRect brect = CGRectMake(0, 330, 60, 27);
CGPoint centern=CGPointMake(CGRectGetMidX(brect), CGRectGetMidY(brect) );
UIButton * button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setFrame:brect];
[button setCenter:centern];
[button setTitle:#"New" forState:UIControlStateNormal];
[self addSubview:button];
}
The button is drawn without the inside text. Only when I touch the display is it then drawn properly.
My solution has been the following: Give the tab-bar controller two UIViewControllers to control. Then force a draw of the second one, and then back to the first one:
tabBarController.selectedViewController = [tabBarController.viewControllers objectAtIndex:1];
tabBarController.selectedViewController = [tabBarController.viewControllers objectAtIndex:0];
This solution works, but it's a stupid hack. Is there a better way to do this?
The code you posted dosen't seem to fit inside a drawRect: implementation. You should add this code to your views init implementation, or to your viewControllers loadView or viewDidLoad implementation.
These two lines do not do anything useful you can leave them out:
CGPoint centern=CGPointMake(CGRectGetMidX(brect), CGRectGetMidY(brect) );
[button setCenter:centern];
Whenever you set a frame on a view, the views center is implicitly set to the frames midpoint(center).

How do you add more than one UIBarButton on UINavigationItem.rightBarButtonItem (or leftBarButtonItem)?

I have tried this approach/hack:
http://blog.blackwhale.at/2009/06/uibuttons-in-uinavigationbar/
The problem is this leaves a faint seam. I tried setting the background image of the nested toolbar to an image I captured of what it should be. That didn't work. The image was not applied. I have also tried using a nested UINavigationBar and that didn't seem to work.
I have seen this done in several iPhone apps. Does anyone know how?
[EDIT] I want the buttons to look like normal UIBarButtonItems and be able to use system styles like UIBarButtonSystemItemAdd, UIBarButtonSystemItemRefresh. The link I provided does this except you can see a faint seam because it is a UIToolbar nested in the navigationbar..
Please don't mention this breaking the Human Interface Guidelines. (We know).
I appreciate you contributing your hacks... thats the only way to do this!
iOS 5.0 now supports multiple buttons. See the iOS documentation for UINavigationItem. Specifically, the following:
Properties:
#property(nonatomic, copy) NSArray *leftBarButtonItems;
#property(nonatomic, copy) NSArray *rightBarButtonItems;
#property BOOL leftItemsSupplementBackButton;
Methods:
- (void)setLeftBarButtonItems:(NSArray *)items animated:(BOOL)animated;
- (void)setRightBarButtonItems:(NSArray *)items animated:(BOOL)animated;
I posted code to add two buttons to the right of the navigationBar. You can set barStyle = -1 instead of subclassing UIToolbar.
To get rid of the background ('seam') of a UIToolbar, create a subclass of UIToolbar and override the (void)drawRect:(CGRect)rect method. Leave that blank and your UIToolbar will no longer have a background.
Just used this in my own project and worked great. Found this in the comments of: http://osmorphis.blogspot.com/2009/05/multiple-buttons-on-navigation-bar.html
UIView *parentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, myWidth, myHeight)];
// make UIView customView1... (UILabel, UIButton, etc.) with desired frame and settings
[parentView addSubview:customView1];
[customView1 release];
// make UIView customView2... (UILabel, UIButton, etc.) with desired frame and settings
[parentView addSubview:customView2];
[customView2 release];
UIBarButtonItem *customBarButtomItem = [[UIBarButtonItem alloc] initWithCustomView:parentView];
[parentView release];
self.navigationItem.rightBarButtonItem = customBarButtomItem;
[customBarButtomItem release];
see uicatalogue example available at apple's site for free...they used uisegmented control to show three buttons in place of right bar button on navigaion bar...
I can't comment but in addition to #iworkinprogress I had to set the UIToolbar background color to clear:
[toolbar setBackgroundColor:[UIColor clearColor]];
This was also found in the comments of http://osmorphis.blogspot.com/2009/05/multiple-buttons-on-navigation-bar.html.
In iOS 4.x the clearColor seems to have no effect on the UIToolbar, whereas overriding its drawRect: did.
I came up with a helper function I'm using all over my project. Basically it checks if there is already a button on the bar and either add the new one or merge it with existing buttons. So you can call the function just once or multiple times:
+ (void)AddButtonToBar:(UIViewController *)controller withImage:(NSString *)imageName withAction:(SEL)action withFrame:(CGRect) frame{
UIButton *newButton =[[UIButton alloc] init];
[newButton setBackgroundImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
newButton.frame = frame;
[newButton addTarget:controller action:action forControlEvents:UIControlEventTouchUpInside];
if ([[controller.navigationItem rightBarButtonItems] count] == 0)
[controller.navigationItem setRightBarButtonItem:[[UIBarButtonItem alloc] initWithCustomView:newButton]];
else {
NSMutableArray *existingButtons = [[NSMutableArray alloc] initWithArray:[controller.navigationItem rightBarButtonItems]];
[existingButtons addObject:[[UIBarButtonItem alloc] initWithCustomView:newButton]];
[controller.navigationItem setRightBarButtonItems:(NSArray *)existingButtons];
}
}
Call it from the view controller:
[Helper AddButtonToBar:self withImage:#"imageName.png" withAction:#selector(myAction) withFrame:CGRectMake(0, 0, 24, 24)];

UIButton in UITableView cell like "Delete Event"

I'd like to add a button to a table cell. The "Delete Event" in the calendar app inspired me... (a similar case is "Share Contact" in contacts)
As of now there's
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//..yadayadayada
cell = [tableView dequeueReusableCellWithIdentifier:#"buttonCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:#"buttonCell"] autorelease];
}
UIButton *button = [UIButton buttonWithType:UIButtonTypeInfoDark];
[button setBackgroundColor:[UIColor redColor]];
button.titleLabel.text = #"Foo Bar";
[cell.contentView addSubview:button];
which produces a button, indeed. It doesn't look yet how it's supposed to (it's obvious I've never dealt with buttons in iPhone, yet), but is this at least the right approach?
The way you have it now each time a cell is shown you're allocating a button, setting its value, and adding it to the cell's contentView. When the cell gets reused (via dequeueReusableCellWithIdentifier) you'll be creating another new button, adding it to the cell (on top of the old one) etc. The fact that it's gone through addSubview but no explicit release means each button's retain count will never go to zero so they'll all stick around. After a while of scrolling up and down the cell will end up with hundreds of button subviews which probably isn't what you want.
A few tips:
Never allocate stuff inside a cellForRowAtIndexPath call unless it's done when dequeueReusableCellWithIdentifier is returning nil and you're initializing the cell. All other subsequent times you'll be handed back the cached cell that you will have already set up so all you have to do is change the labels or icons. You're going to want to move all that button allocation stuff up inside the if conditional right after the cell allocation code.
The button needs to have a position and also a target set for it so it'll do something when tapped. If every cell is going to have this button a neat trick is to have them all point to the same target method but set the button's tag value to the indexPath.row of the cell (outside the cell allocation block since it varies for each cell). The common tap handler for the button would use the tag value of the sender to look up the underlying data in the dataSource list.
Call release on the button after you've done an addSubview. That way the retain count will fall to zero and the object will actually get released when the parent is released.
Instead of adding the button via addSubview, you can return it as the accessoryView for the cell so you don't have to worry about positioning it (unless you're already using the accessoryView for something else -- like disclosure buttons).
I took a different approach to creating an equivalent to the 'Delete Event' button in the Calendar app. Rather than add a button as a subview, I added two background views (red and darker red, with nice gradients) to the cells and then rounded off the corners and set the border to grey.
The code below creates a re-usable cell (in the usual fashion). The two images referred to ('redUp.png' and 'redDown.png') were taken from a screenshot of the calendar's 'Delete Event' button. (That seemed quicker than creating the gradients programmatically.) There's scope for a bit more fine tuning to get it even closer to the Calendar's 'Delete Event' appearance, but this is pretty close.
The button's action is triggered by the tableView delegate method tableView:didSelectRowAtIndexPath: method.
// create a button from a table row like the Calendar's 'Delete Event' button
// remember to have an #import <QuartzCore/QuartzCore.h> some above this code
static NSString *CellWithButtonIdentifier = #"CellWithButton";
UITableViewCell *cell = [self dequeueReusableCellWithIdentifier:CellWithButtonIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellWithButtonIdentifier] autorelease];
[[cell textLabel] setTextAlignment: UITextAlignmentCenter];
UIImageView* upImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"redUp.png"]];
UIImageView* downImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"redDown.png"]];
[cell setBackgroundView: upImage];
[cell setSelectedBackgroundView: downImage];
[[upImage layer] setCornerRadius:8.0f];
[[upImage layer] setMasksToBounds:YES];
[[upImage layer] setBorderWidth:1.0f];
[[upImage layer] setBorderColor: [[UIColor grayColor] CGColor]];
[[downImage layer] setCornerRadius:8.0f];
[[downImage layer] setMasksToBounds:YES];
[[downImage layer] setBorderWidth:1.0f];
[[downImage layer] setBorderColor: [[UIColor grayColor] CGColor]];
[[cell textLabel] setTextColor: [UIColor whiteColor]];
[[cell textLabel] setBackgroundColor:[UIColor clearColor]];
[cell setBackgroundColor:[UIColor clearColor]]; // needed for 3.2 (not needed for later iOS versions)
[[cell textLabel] setFont:[UIFont boldSystemFontOfSize:20.0]];
[upImage release];
[downImage release];
}
return cell;
Yes, this is generally the correct approach.
A tip:
Set the callback for your button events, so that it actually does something when clicked.
[myButton addTarget:self action:#selector(whatMyButtonShouldDo:)
forControlEvents:UIControlEventTouchUpInside];
EDIT: Adjusted code for using a system button, which renders a lot of what I had written uneccessary.
Yes, you are on the right track, that's the easiest way to add a subview to a cell (the other is subclassing a UITableViewCell).
Check the Apple guide for more info.
To avoid positioning and memory management hassles you can create a specialized cell class and link to a XIB. That sounds like the cleanest approach to me. Here is the link:
http://icodeblog.com/2009/05/24/custom-uitableviewcell-using-interface-builder/