Touch-To-Focus on cameraOverlayView in iOS 5? - iphone

I used to have touch to focus on my cameraOverlayView (in UIImagePickerController), but once I updated to iOS 5 it doesn't work.
I used a custom view class which I applied to my view in cameraOverlayView.
I made sure everything is connected and applied in Interface Builder.
I don't want to show camera controls. (imagePicker.showsCameraControlls = NO).
This is my code in the OverlayView class:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UIView * previewView = [[[[[[[[[[
self.picker.view // UILayoutContainerView
subviews] objectAtIndex:0] // UINavigationTransitionView
subviews] objectAtIndex:0] // UIViewControllerWrapperView
subviews] objectAtIndex:0] // UIView
subviews] objectAtIndex:0] // PLCameraView
subviews] objectAtIndex:0]; // PLPreviewView
[previewView touchesBegan:touches withEvent:event];
NSLog(#"Should Focus");
}
Do you guys have a different tap-to-focus method on an overlay?
Or do you know how to fix this?
THANK YOU SO MUCH IN ADVANCE!

because ios5.0
Touch events are not getting forwarded to the view in the cameraOverlayView property of UIImagePickerController.
please changed you init code like this:
// self.cameraOverlayView = [[UIView alloc] init];
// [self.cameraOverlayView addSubview:previewView];
[self.view addSubview:mask];
[self.view bringSubviewToFront:previewView];

Related

Pass specific touch events in UIView/UIWindow to the next responder (sendEvent:/hitTest: doesn't work )

I'm trying to capture all the touch events in a UIKeyboard without breaking its functionality. I've tried:
HitTest:
UIGestureRecognizer
Adding a UIWindow in the top and pass events to next responder
However, none of these worked.
And it seems that we aren't allowed to sub class the UIKeyboard.
Can you think of any method that may work?
Thanks,
Peak
Update:
Lets simplify the problem: How can I add a UIView or UIWindow that passes specific touch events to the next responder(just like setting the userInteractionEnabled to NO)?
Heres my code(of course can't work...):
- (void)sendEvent:(UIEvent *)event {
NSLog(#"%#",event);
//if ([self eventIsNoteworthy:event]) [self extraEventHandling:event];
[super sendEvent:event]; // Apple says you must always call this!
}
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint hitPoint = [underneathButton convertPoint:point fromView:self];
if ([underneathButton pointInside:hitPoint withEvent:event]) return underneathButton;
NSLog(#"Called");
return [super hitTest:point withEvent:event];
//return mainWindow;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"Start");
[self.nextResponder touchesBegan:touches withEvent:event];
}
I'm not sure if it's possible or not. A couple of things come to mind. One is to recreate the UIKeyboard yourself and then capture the touches that way. Apple Docs stating it's possible.
The other would be to re-look at how you added the UIWindow overtop of the keyboard. It's my understanding that the keyboard view is somehow layered differently over your app so you might not have had the UIWindow overtop of the keyboard (ref). That link gives a solution for getting a reference to the actual UIView for the keyboard, from there you'd want to place your UIWindow onto the parent of the keyboard view.
To help, here's a script that you can use to dump out all the views to see how they're being layered:
static void dumpViews(UIView* view, NSString *text, NSString *indent)
{
Class cl = [view class];
NSString *classDescription = [cl description];
while ([cl superclass])
{
cl = [cl superclass];
classDescription = [classDescription stringByAppendingFormat:#":%#", [cl description]];
}
if ([text compare:#""] == NSOrderedSame)
NSLog(#"%# %#", classDescription, NSStringFromCGRect(view.frame));
else
NSLog(#"%# %# %#", text, classDescription, NSStringFromCGRect(view.frame));
for (NSUInteger i = 0; i < [view.subviews count]; i++)
{
UIView *subView = [view.subviews objectAtIndex:i];
NSString *newIndent = [[NSString alloc] initWithFormat:#" %#", indent];
NSString *msg = [[NSString alloc] initWithFormat:#"%#%d:", newIndent, i];
dumpViews(subView, msg, newIndent);
[msg release];
[newIndent release];
}
}
Call it like this: dumpViews([[UIApplication sharedApplication] keyWindow], #"", #"");
Hope that helps some! :D
Take a look at this answer about accessing the (private view hierarchy that contains the) UIKeyboard. N.b. this is unsupported in iOS, and Apple rejects apps that do this. (Interfering with private view hierarchies inside UIKit is tantamount to using private API.)

CALayer/UIView crashes when I touch the UIVIew

Iam using a custom layer for drawing on my UIView. And to invalidate and update the layer I need to set my view to be the delegate of my CALayer. When this is done it draws my text correctly but when the view receives touches the app crashes.
What I am a doing wrong?
- (void) layoutSubView {
if (drawLayer == nil) {
drawLayer = [[CALayer alloc] init];
[drawLayer setDelegate:self];
[[self layer] addSublayer:drawLayer];
}
[drawLayer setFrame:[self bounds]];
[drawLayer setNeedsDisplay];
}
Above is my code to setup the CALayer, and below is the overridden drawLayer method.
- (void) drawLayer:(CALayer *)layer
inContext:(CGContextRef)ctx {
if (layer == drawLayer) {
UIGraphicsPushContext(ctx);
[self drawTitle:ctx];
UIGraphicsPopContext();
}
}
Here is the stack.
You can't use a UIView object to be the delegate of a layer other than its own layer. Its stated here. You will need to assign some other object as its delegate.

Touch Began method issue?

I have created an app in which I want to use the touchbegan method. For this I have used the following code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//Get all the touches.
NSSet *allTouches = [event allTouches];
//Number of touches on the screen
if([allTouches count] > 0 && self.view!=comparedView.view)
{
//Navigate to next screen here likewise you generally do
ActionViewController *actController = [[ActionViewController alloc] init];
UINavigationController *nvController = [[UINavigationController alloc]
initWithRootViewController:actController];
nvController.navigationBarHidden = YES;
CGRect frame = [[nvController navigationBar] frame];
frame = CGRectMake(0.0,0.0, 320.0, 460.0);
[[nvController navigationBar] setFrame:frame];
[self setView:[nvController view]];
[actController release];
}
}
On touch on anywhere on screen for my 1st view the following code will be executed and it will go to the next view. But problem is that when i go to the last view and touch on the imageview in that view the above method is called.
I am not getting how it is taking place.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event will be called every time till the view controller get released or touch events are disabled.
the view are being created and added to the same view conroller every time so the touch beganis called for all the views

Hide/Show iPhone Camera Iris/Shutter animation

I am not able to Hide the iphone Camera shutter opening animation for my app.
I am using UIImagePickerController to access iphone camera and using my own overlay controllers.
Is there a way to remove the initial shutter(also known as Iris) animation as the camera starts.
Thank You
[EDIT]
For those who wants to know the way to change the camera iris animation.
The below function is called before the camera iris animation starts.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// Here is were I make the camera preview fit the entire screen.
// This might violate the "don't change the view hierarchy"-rule.
// So I am not sure if it is valid for App Store commitment.
// However, the NSLogs are used to
// figure out which subview is the actual Camera Preview which turns out
// to be the PLPreviewView. (uncomment to se the printouts).
// Change it's size to fit the entire screen (and scale it accordingly
// to avoid distorted image
NSLog(#"WillShowViewController called...");
NSLog(#"VC:view:subviews\n %#\n\n", [[viewController view] subviews]);
NSLog(#"VC:view:PLCameraView:subviews\n %#\n\n", [[[[viewController view] subviews] objectAtIndex: 0] subviews]);
NSLog(#"VC:view:PLCameraView:PLPreviewView:subviews\n %#\n\n", [[[[[[viewController view] subviews] objectAtIndex: 0] subviews] objectAtIndex: 0] subviews]);
NSLog(#"VC:view:PLCameraView:PLCropOverLay:subviews\n %#\n\n", [[[[[[viewController view] subviews] objectAtIndex: 0] subviews] objectAtIndex: 1] subviews]);
NSLog(#"VC:view:PLCameraView:UIImageView:subviews\n %#\n\n", [[[[[[viewController view] subviews] objectAtIndex: 0] subviews] objectAtIndex: 2] subviews]);
}
In the above function you can go through each layer by using the normal NSMuatableArray syntax like objectAtIndex
hope this might help you.
Regards,
Ankur
Using this answer as a starting point, I've finally solved this problem:
NOTE: This is obviously not 3.3.1-compliant.
Listen for the UINavigationControllerDidShowViewControllerNotification on your UIImagePickerController, and the PLCameraViewIrisAnimationDidEndNotification globally.
Traverse the view hierarchy (starting at the main UIWindow) looking for the PLCameraView. Save the index of the view against the main UIWindow, as you'll need it later.
Remove the PLCameraView from its superView. If desired, insert your own view at global index 0.
When the iris animation is finished, remove your view and re-add the PLCameraView at its original index.
Came across a similar: I wanted to have the shutter appear when I take the picture triggered by a button in a self.cameraOverlayView of a UIImagePickerController. Arrived to this page, did some extra research and came to this solution.
Synopsis:
#interface MyController : UIImagePickerController
...
- (id) init {
...
self.cameraOverlayView = _my_overlay_;
self.showsCameraControls = NO;
...
}
...
- (void) onMyShutterButton {
[self takePicture];
// You want the shutter animation to happen now.
// .. but it does not.
}
Solution:
// Some constants for the iris view and selector
NSString* kIrisViewClassName = #"PLCameraIrisAnimationView";
SEL kIrisSelector = NSSelectorFromString(#"animateIrisOpen");
#implementation MyController {
...
UIView* iris_;
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Find the iris view in the siblings of your overlay view
for (UIView* view in self.cameraOverlayView.superview.subviews) {
if ([kIrisViewClassName isEqualToString:[[view class] description]]) {
// It will be hidden by 'self.showsCameraControls = NO'.
view.hidden = false;
// Extra precautions - as this is undocumented.
if ([view respondsToSelector:kIrisSelector]) {
iris_ = view;
}
break;
}
}
}
- (void) animateIrisOpen {
if (iris_) {
[iris_ performSelector:kIrisSelector];
}
}
...
- (void) onMyShutterButton {
[self takePicture];
[self animateIrisOpen]; // Voila - the shutter happens
}
I've messed around with this a bit, but sending various combinations of the view lifecycle methods to the image picker. (viewWillAppear, viewDidAppear, etc.) But I don't remember which ones ended up working.
Sorry for replying getting back so late. I found out the solution for that well I played around with the view hierarchy of the cameraView and added my own layer at the top of everything. The animation took place there and once the shutter was open the top most layer was removed. If someone need any further help with the code please let me know, I will provide with the exact steps and syntax.
-Ankur
joshwa's answer completely hides the entire camera view for the duration of the iris animation. For my purposes, I needed the camera view visible, just without the iris animation. I was able to accomplish this with a little tweaking of his method. As others have noted, this may or may not be allowed on the app store since we're messing with the view hierarchy as well as listening for undocumented notifications.
3 ivars are needed:
UIImagePickerController *imagePickerController;
UIView *plCameraIrisAnimationView; // view that animates the opening/closing of the iris
UIImageView *cameraIrisImageView; // static image of the closed iris
Hide the closed iris image and remove the animation view. I tried simply hiding the animation view as well, but the animation was still visible:
- (void)receivedNavigationControllerDidShowViewControllerNotification:(NSNotification *)notification {
UIView *view = imagePickerController.view;
[plCameraIrisAnimationView release];
plCameraIrisAnimationView = nil;
cameraIrisImageView = nil;
while (view.subviews.count && (view = [view.subviews objectAtIndex:0])) {
if ([[[view class] description] isEqualToString:#"PLCameraView"]) {
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[UIImageView class]]) {
cameraIrisImageView = (UIImageView *)subview;
}
else if ([[[subview class] description] isEqualToString:#"PLCropOverlay"]) {
for (UIView *subsubview in subview.subviews) {
if ([[[subsubview class] description] isEqualToString:#"PLCameraIrisAnimationView"]) {
plCameraIrisAnimationView = [subsubview retain];
}
}
}
}
}
}
cameraIrisImageView.hidden = YES;
[plCameraIrisAnimationView removeFromSuperview];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UINavigationControllerDidShowViewControllerNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedPLCameraViewIrisAnimationDidEndNotification:) name:#"PLCameraViewIrisAnimationDidEndNotification" object:nil];
}
When the animation is over, unhide the iris image and re-add the animation view:
- (void)receivedPLCameraViewIrisAnimationDidEndNotification:(NSNotification *)notification {
cameraIrisImageView.hidden = NO;
UIView *view = imagePickerController.view;
while (view.subviews.count && (view = [view.subviews objectAtIndex:0])) {
if ([[[view class] description] isEqualToString:#"PLCameraView"]) {
for (UIView *subview in view.subviews) {
if ([[[subview class] description] isEqualToString:#"PLCropOverlay"]) {
[subview insertSubview:plCameraIrisAnimationView atIndex:1];
[plCameraIrisAnimationView release];
plCameraIrisAnimationView = nil;
break;
}
}
}
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"PLCameraViewIrisAnimationDidEndNotification" object:nil];
}
The below function is called before the camera iris animation starts.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// Here is were I make the camera preview fit the entire screen.
// This might violate the "don't change the view hierarchy"-rule.
// So I am not sure if it is valid for App Store commitment.
// However, the NSLogs are used to
// figure out which subview is the actual Camera Preview which turns out
// to be the PLPreviewView. (uncomment to se the printouts).
// Change it's size to fit the entire screen (and scale it accordingly
// to avoid distorted image
NSLog(#"WillShowViewController called...");
NSLog(#"VC:view:subviews\n %#\n\n", [[viewController view] subviews]);
NSLog(#"VC:view:PLCameraView:subviews\n %#\n\n", [[[[viewController view] subviews] objectAtIndex: 0] subviews]);
NSLog(#"VC:view:PLCameraView:PLPreviewView:subviews\n %#\n\n", [[[[[[viewController view] subviews] objectAtIndex: 0] subviews] objectAtIndex: 0] subviews]);
NSLog(#"VC:view:PLCameraView:PLCropOverLay:subviews\n %#\n\n", [[[[[[viewController view] subviews] objectAtIndex: 0] subviews] objectAtIndex: 1] subviews]);
NSLog(#"VC:view:PLCameraView:UIImageView:subviews\n %#\n\n", [[[[[[viewController view] subviews] objectAtIndex: 0] subviews] objectAtIndex: 2] subviews]);
}
In the above function you can go through each layer by using the normal NSMuatableArray syntax like objectAtIndex
hope this might help you.
Regards,
Ankur
To expound on Catalin's answer, (which was great btw), I found if you change the method "animateIrisOpen" slightly, the presentation is a fraction better... but noticeable.
- (void) animateIrisOpen {
if (iris_) {
iris_.hidden = NO;
[iris_ performSelector:kIrisSelector];
}
}

Ignore touches to background (super) view

How should I be handling, or rather NOT handling (ignoring), touches to my background view? It happens to be the view of my View Controller which has subviews (objects) that I DO want to respond to touch events. Setting userInteractionEnabled = NO for the view seems to turn off ALL interaction for the subviews as well.
I'm currently testing for
if ([[touch view] superview] == self.view)
in touchesBegan/Moved/Ended. But I'm trying to eliminate some conditional testing so looking for a better way...
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; recursively calls -pointInside:withEvent:. point is in frame coordinates
If you override that in your background view, you can specify which subview gets hit - you can be lazy and just ask each of your subviews if they would return yes to it, and never return yourself (return nil if they all return nil). Something like:
UIView *hitView = nil;
NSArray *subviews = [self subviews];
int subviewIndex, subviewCount = [subviews count];
for (int subviewIndex = 0; !hitView && subviewIndex < subviewCount; subviewIndex++) {
hitView = [[subviews objectAtIndex:subviewIndex] hitTest:point withEvent:event];
}
return hitView;
Thanks for the answer Dan, great question too.
However the accepted answer has an error: hitTesting subviews should be done with the point converted to the subview.
Also, subviewIndex is already defined before 'for'.
Since subviews are ordered according to their z index, iterating should go from the last to the first one (see Event handling for iOS - how hitTest:withEvent: and pointInside:withEvent: are related? and How to get UIView hierarchy index ??? (i.e. the depth in between the other subviews)).
Here's the updated code:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = nil;
NSArray *subviews = [self subviews];
int subviewIndex, subviewCount = [subviews count];
for (subviewIndex = subviewCount-1; !hitView && subviewIndex >= 0; subviewIndex--) {
UIView *subview = [subviews objectAtIndex:subviewIndex];
hitView = [subview hitTest:[self convertPoint:point toView:subview] withEvent:event];
}
return hitView;
}