2

I have a UIImagePickerController of type camera when the user lands on a page. I have put a custom camera overlay on top of the UIImagePickerController, but none of the button events are firing. I know most of this code is from a base Apple code, so I am perplexed as to what is happening.

@interface TakePhotoViewController : UIImagePickerController <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
...

- (void)showImagePickerForSourceType:(UIImagePickerControllerSourceType)sourceType
{
if (self.capturedImages.count > 0)
{
    [self.capturedImages removeAllObjects];
}

self.sourceType = sourceType;
self.delegate = self;

if (sourceType == UIImagePickerControllerSourceTypeCamera)
{
    /*
     The user wants to use the camera interface. Set up our custom overlay view for the camera.
     */
    self.showsCameraControls = NO;

    /*
     Load the overlay view from the OverlayView nib file. Self is the File's Owner for the nib file, so the overlayView outlet is set to the main view in the nib. Pass that view to the image picker controller to use as its overlay view, and set self's reference to the view to nil.
     */
    [[NSBundle mainBundle] loadNibNamed:@"CameraOverlay" owner:self options:nil];
    float scale = [self getScaleForFullScreen];
    self.cameraViewTransform = CGAffineTransformMakeScale(scale, scale);
    self.overlayView.frame = self.cameraOverlayView.frame;
    self.cameraOverlayView = self.overlayView;
    self.overlayView = nil;
}

}

The CameraOverlay's owner is TakePhotoViewController. One of the buttons inside of the CameraOverlay sends a Touch Up Inside event to the outlet function listed below. The code in the TakePhotoViewController attached to a button is below, of which no logs are firing:

- (IBAction)outlet:(id)sender {
NSLog(@"outlet");
}
Chris Dunn
  • 23
  • 3
  • 1
    Where is the code that hooks the button to this target and this action method? – matt Mar 04 '14 at 05:02
  • I have control-dragged the button to this action method from the CameraOverlay. Next to the outlet method the circle is filled in and it says "CameraOverlay.xib - Outlet Button Test" – Chris Dunn Mar 05 '14 at 02:45
  • Nevertheless, I am skeptical. After all, you said yourself that the `outlet` method is not firing. I urge you to form this action connection in code - just after you say `self.cameraOverlayView = self.overlayView;`, call `addTarget:action:forControlEvents:` on the button, to form the connection. – matt Mar 05 '14 at 02:50
  • Hmm. There's gotta be something bigger I am missing here, as that is not working either. [self.takePhotoButton addTarget:self action:@selector(outlet:) forControlEvents:UIControlEventTouchUpInside]; And takePhotoButton is defined in the CameraOverlay.xib. When I click next to the filled black button next to the method, it even brings me to the button in the CameraOverlay. – Chris Dunn Mar 05 '14 at 03:40
  • Is it possible that `self.takePhotoButton` itself is nil in that code? Add NSLog to see... – matt Mar 05 '14 at 03:46
  • You see, those filled buttons ("connections" in Xcode) can be misleading. So it would be nice to be 100% certain, _in code_, that the action connection is being formed. If it is, then we can start to suspect some other cause, like maybe some invisible view is lying on top of the button so that you can't tap it... – matt Mar 05 '14 at 03:49
  • Here's another idea: say `[self.takePhotoButton addTarget:self action:@selector(zampabalooie:) forControlEvents:UIControlEventTouchUpInside];` If you do not crash when you tap the button (because there is no `zampabalooie:` method), then either you are not able to tap the button or `self.takePhotoButton` wasn't this button. – matt Mar 05 '14 at 03:53
  • if(self.takePhotoButton) passes. In debug mode, the UIButton has a memory address. However, the _backgroundView and _imageView for the button are nil. Not sure if that means anything. This may be a dumb question but is there a sense of a view being on top of and obscuring another view? This is how the custom overlay is being accomplished: [[NSBundle mainBundle] loadNibNamed:@"CameraOverlay" owner:self options:nil]; self.overlayView.frame = self.cameraOverlayView.frame; self.cameraOverlayView = self.overlayView; self.overlayView = nil; The view is coming up too. – Chris Dunn Mar 05 '14 at 03:54
  • More ideas... Does the button highlight when you tap it? i.e. does it _look_ like you are successfully tapping it? Is `userInteractionEnabled` NO for the button or for its containing `overlayView`? – matt Mar 05 '14 at 03:59
  • It doesn't look like the button is being tapped. I did programmatically add in the code: [self.takePhotoButton sendActionsForControlEvents:UIControlEventTouchUpInside]; And did see the function get called. I will take a look at these other suggestions as well as making sure Interaction is enabled on these elements. Thanks for all the help thus far. Really appreciate it. – Chris Dunn Mar 05 '14 at 04:04
  • When and how are you presenting the image picker "when the user lands on a page"? You are not doing it in `viewDidLoad`, are you, by any chance? – matt Mar 05 '14 at 04:08
  • Argh, I am presenting it in viewDidLoad - (void)viewDidLoad { [super viewDidLoad]; [self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera ]; } I did try to move this into viewDidAppear, but maybe that's not the right place for this either? – Chris Dunn Mar 05 '14 at 04:22
  • If you put it into `viewDidAppear:` instead, that doesn't help? Because `viewDidLoad` is way too early - your view is not even in the interface yet, so you're in for trouble. – matt Mar 05 '14 at 04:23
  • I have another idea - try giving the overlayView a distinctive background color to make sure that, when it appears, it really has the frame you think it should have. I'm thinking, what if it has a zero frame? Its subviews would become untappable... – matt Mar 05 '14 at 04:26
  • I gave the overlay an orange background color, and when it appears on my phone, the orange color is nowhere to be seen (only the buttons appear over whatever my camera is viewing). I am a bit unsure as to what you mean by 0 frame, but it seems like that is the case here. – Chris Dunn Mar 05 '14 at 04:36
  • I think we've got it! – matt Mar 05 '14 at 04:40
  • Okay, I have now actually ventured to answer the original question. I wish I'd suggested this background color idea when I thought of it, about an hour ago... :)))) – matt Mar 05 '14 at 04:48
  • Perfect! That is the issue. The one question I had is how to fix it. I thought the cameraOverlayView is a property that is set as part of the UIImagePickerController or do I need to explicitly do that in the init? – Chris Dunn Mar 05 '14 at 04:50
  • By default, a UIImagePickerController has no `cameraOverlayView`. Your goal is to add one (and indeed you are doing so). All of that is fine. I don't think it matters _when_ you add it, but when you set its frame might matter, and certainly the value of that frame matters. If you look at sample code you'll see various ways of calculating a reasonable frame for the overlay view. – matt Mar 05 '14 at 04:55

2 Answers2

5

The problem is this line:

self.overlayView.frame = self.cameraOverlayView.frame;

Our cameraOverlayView is nil; it has no frame, because it doesn't even exist yet. So we are giving the overlayView a zero frame. It is dimensionless. The button appears, but it has a zero-sized superview and therefore cannot be tapped.

Now, you may ask, why is that? It's because of the way touch delivery works in iOS. To be touchable, a subview's superview must also be touchable, because the superview is hit-tested for the touch before the subview. But a zero-size view is not touchable: your touch misses the overlayView, so it misses the button as well.

The solution might be as simple as giving the overlayView a reasonable real frame. There may also be timing problems about when you do that (e.g. it may be that you cannot really set the frame until the image picker is about to appear - viewWillAppear:), but in any case this is the place to start.

matt
  • 515,959
  • 87
  • 875
  • 1,141
0

I ran across another way this can happen, at least if you're not subclassing UIImagePickerController (Apple says not to subclass it).

I created a "helper" class that creates, configures and presents the UIImagePickerController in a show() method of the helper class. When I created the helper class, I did so simply inside a method, thinking that UIImagePickerController would be displayed modally and synchronously.

So after presenting the UIImagePickerController, which indeed appeared, my helper class was then destroyed resulting in no button handlers that could be called from my custom overlay within the UIImagePickerController.

So for my fix, I simply had to construct my helper class as a member property of the class that wants to call the helper's show().

Smartcat
  • 2,834
  • 1
  • 13
  • 25