Challenge - touch image to bring up alert


#1

I want to be able to touch the image to bring up an alert to delete the photo. The method below does exactly that in a different app however, it doesn’t work in Homepwner and I’m not sure why.
My guess is that it has to do with the first responder but after looking at the UIResponder class reference I’m not sure what to do about it…

[code]-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touchMoved = [touches anyObject];
CGPoint location = [touchMoved locationInView: [self view]];

if (CGRectContainsPoint(imageView.bounds, location)) {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@""  message:@"Delete photo?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
    
    [alert show];
    [alert release];
}

}[/code]


#2

Hi,

In the ItemDetailViewController.xib the container is a UIControl rather than a UIView

A UIControl has different methods for capturing touch events which is why this code doesn’t get triggered.

If you select the Control container in the xib and then in the Identity Inspector change it from a UIControl to a UIView then your touchesBegan should then be triggered.

However, the touchUpInside event that is connected to the backgroundTapped method won’t be recognised by a UIView (and will throw an exception) so you will also need to remove this connection and replace the functionality (see below for one way to do this)

Also - it should be imageView.frame instead of imageView.bounds to get the desired result.

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touchMoved = [touches anyObject];
    CGPoint location = [touchMoved locationInView: [self view]];
    
    if (CGRectContainsPoint(imageView.frame, location)) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@""  message:@"Delete photo?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
        
        [alert show];
        [alert release];
    }
    else
        [[self view] endEditing:YES];      //  replaces backgroundTapped
}

HTH
Gareth


#3

So, I made the suggested changes, and it works! Thank you!

I see now that UIControl does not inherit from UIViewController which I thought explained why touchesBegan:withEvent: wasn’t recognized. But not so fast, because UIControl does inherit from UIResponder which is where the method is declared. According to the touchesBegan:withEvent: documentation, “…immediate UIKit subclasses of UIResponder, particularly UIView, forward the message up the responder chain.” Now, UIViewController is also an immediate subclass of UIResponder, but it isn’t specifically mentioned here. In fact, there are only three immediate subclasses of UIResponder and the documentation makes it sound like more than one is capable of forwarding the message. Though it does seem to favor UIView.

I know I’m making this much harder than it needs to be, but mostly I’m just trying to get a handle on deciphering the documentation. So, if I had to ask only one question it would be…is it possible to determine whether or not UIViewController can forward touchesBegan:withEvent: messages up the responder chain using only the documentation?


#4

Hi,

Sorry about that my explanation in my previous post wasn’t right. :blush:

I found this in the View Controller User Guide:

[quote]View controllers are themselves descendants of the UIResponder class and are therefore capable of handling all sorts of events. Normally, when a view does not respond to a given event, it passes that event to its superview. However, if the view is being managed by a view controller, it passes the event to the view controller object first. This gives the view controller the opportunity to absorb any events that are not handled by its views. If the view controller does not handle the event, then that event moves on to the view’s superview as usual.
[/quote]

So it looks like there is a specific piece of logic in UIView that sends the event to the ViewController. In our case (when we used a UIControl) that logic doesn’t exist and hence the ViewController doesn’t get the message.

We could introduce the same behaviour my subclassing UIControl, give it an ivar that points to the viewController, set the ivar in viewDidLoad and then override the touchesBegan method to forward it to the viewController [myViewController touchesBegan:touches withEvent:event];

That feels a bit clunky though and I’m not sure that answers your question - I will do a bit more research.

Gareth


#5

Actually, isn’t your original explanation almost exactly right?..[quote]A UIControl has different methods for capturing touch events which is why this code doesn’t get triggered.[/quote]
There are indeed different methods for capturing touch events in a UIControl. (Unsurprisingly, I couldn’t get beginTrackingWithTouch:withEvent: to work either, but that’s a challenge for another day.)

Still, that doesn’t explain why UIControl doesn’t inherit the touchesBegan method. I didn’t even know it was possible for a subclass to not respond to inherited methods.

In the words of my two-year-old, “that’s cheating!”


#6

Hi,

Still pursuing this but what I’ve done so far.

I subclassed UIControl and it does get the touchesBegan and it also gets beginTrackingWithTouch which is as you would expect from the class hierarchy.
(You can’t not respond but you can override a method to do nothing).

Now the responder chain works by calling hitTest:withEvent: which returns [quote]The view object that is the farthest descendant of the current view and contains point[/quote]

So in my subclassed UIControl I can override the hitTest:WithEvent to return self, see that it is getting called, and all seems to work.

but the documentation says:

so if I return nil from hitTest:WithEvent I would expect the viewController to get the event.

but it doesn’t :frowning: unless you change the UIControl to a UIView.

So from this, my current theory is that UIView has some specific logic in it to pass the touch event to a viewController that is negated in UIControl or maybe UIView has some logic to return it’s viewController that UIControl doesn’t and so it doesn’t know it has a viewController.

Still digging…

Gareth


#7

Hi,

The next thing I tried was looking at the nextResponder when it’s a UIView and when it’s a UIControl and in both cases it is ItemDetailViewController.

I then found a private ivar called _viewDelegate defined in UIView which apparently points to the viewController and again it is set to ItemDetailViewController in both cases.

So as to why it triggers touchesBegan in the viewController when it’s a UIView and not when it’s a UIControl is beyond me.

and to finally answer your one question

No is my answer :confused:

Hoping someone more knowledgeable can add to this though…

Gareth


#8

Thanks again for the effort. That question turned out to be not quite as simple (and far more educational) than I had imagined.


#9

Okay so I’m really late to this party, but I am just now getting here in the book. I can fully appreciate what your trying to do here, with respect to the learning new methods and how to use the documentation, so please don’t take this as criticism or an attack. I more or less just want to float a thought I’m having out to the community for feed back of my own understanding of interface design. In a real world app, I don’t feel like I’d implement the picture deletion this way unless I put in a label or something to let the user know that deletion was available via touching the image. My concern is without that, many users would never realize that they could delete images that way. For simple concise design, wouldn’t it be best to just make use of the toolbar we already have and add a delete image button to it?


#10

I’m no expert, but I think you are absolutely correct - there are much better ways to implement this feature in a real app. I forget now why it even occurred to me - I think the idea came from a Steve Cochan tutorial. Regardless, once I was faced with the problem I really wanted to understand it.