I have a layer, HelloWorldLayer below, where touch works anywhere, but I'd like it to work only when touching a sprite in the layer -- turtle below.
If I try to add self.isTouchEnabled = YES; onto the CCTurtle layer it says
property isTouchEnabled not found on object type CCTurtle
my output reads as follows
2013-01-08 20:30:14.767 FlashToCocosARC[6746:d503] cocos2d: deallocing
2013-01-08 20:30:15.245 FlashToCocosARC[6746:d503] playing walk animation2
Here's my HelloWorldLayer code:
#import "HelloWorldLayer.h"
#import "CCTurtle.h"
#implementation HelloWorldLayer
+(CCScene *) scene
{
CCScene *scene = [CCScene node];
HelloWorldLayer *layer = [HelloWorldLayer node];
[scene addChild: layer];
return scene;
}
-(id) init
{
if( (self=[super init])) {
turtle= [[CCTurtle alloc] init];
[turtle setPosition:ccp(300, 100)];
[self addChild:turtle];
///addChild:child z:z tag:aTag;
self.isTouchEnabled = YES;
turtle. tag=4;
//
}
return self;
}
//- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
//{
// // Processing all touches for multi-touch support
// UITouch *touch = [touches anyObject];
// if ([[touch view] isKindOfClass:[turtle class]]) {
// NSLog(#"[touch view].tag = %d", [touch view].tag);
// [self toggleTurtle];
// }
//}
-(BOOL)containsTouch:(UITouch *)touch {
CGRect r=[turtle textureRect];
CGPoint p=[turtle convertTouchToNodeSpace:touch];
return CGRectContainsPoint(r, p );
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//////GENERAL TOUCH SCREEN
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInView:[touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
[self toggleTurtle];
/////
}
}
-(void) toggleTurtle
{
NSLog(#"playing walk animation2");
[turtle playAnimation:#"walk_in" loop:NO wait:YES];
}
#end
//hello world.h
#import "cocos2d.h"
#import "CCTurtle.h"
#interface HelloWorldLayer : CCLayer
{
CCTurtle *turtle;
}
+(CCScene *) scene;
#end
//CCturtle
#import <Foundation/Foundation.h>
#import "FTCCharacter.h"
#interface CCTurtle : FTCCharacter <FTCCharacterDelegate, CCTargetedTouchDelegate>
{
}
#end
I'm using Cocos2D cocos2d v1.0.1 (arch enabled), and am testing on ipad 4.3 simulator.
with thanks Natalie
ive tried to put the touches directly into ccturtle.m so it can handle its own touches using CCTargetedTouchDelegate as above but using
CCturtle/// I changed files to this trying a different way to find the touched area...
- (CGRect)rect
{
CGSize s = [self.texture contentSize];
return CGRectMake(-s.width / 2, -s.height / 2, s.width, s.height);
}
-(BOOL) didTouch: (UITouch*)touch {
return CGRectContainsPoint(self.rect, [self convertTouchToNodeSpaceAR:touch]);
//return CGRectContainsPoint( [self rect], [self convertTouchToNodeSpaceAR: touch] );
}
-(BOOL) ccTouchBegan:(UITouch*)touch withEvent: (UIEvent*)event {
NSLog(#"attempting touch.");
if([self didTouch: touch]) {
return [self tsTouchBegan:touch withEvent: event];
}
return NO;
}
but still wont compile as still returns the error "Property 'is TouchEnabled' not found on object type 'CCTurtle*'
am really not sure what i can do to get it to run now... and really need to get this working (i suppose i could make invisible buttons, but it would be nicer to be able to find the ccturtle properly and to understand what im doing wrong... hope someone can help
First of all, I cannot see anywhere calling of containsTouch: method. And here are several advices:
Use boundingBox instead of textureRect to get local rect of your node (your turtle, in this case). Or just replace containsTouch: method to your turtle class to incapsulate this. It can be helpful, for example, if you want to make touch area of your turtle bigger/smaller. You will just need to change one little method in your turtle class.
In your ccTouchesBegan:withEvent: method just check for every turtle if it is hit by this touch. Then, for example, you can create dictionary, with touch as the key and array of corresponding turtles as the value. Then you just need to update all turtles positions for moved touch in your ccTouchesMoved:withEvent: method and remove this array of turtles from the dictionary in ccTouchesEnded:withEvent: and ccTouchCancelled:withEvent: method.
If you want your CCTurtle object to accept targeted touches, you can do it by specifying that it conforms to the CCTargetedTouchDelegate protocol. In your CCTurtle #interface, you declare it like so:
#interface CCTurtle : CCNode <CCTargetedTouchDelegate>
Then in the implementation, you tell it to accept touches and implement the ccTouch methods:
#implementation CCTurtle
-(id) init {
.
.
[[CCDirector sharedDirector].touchDispatcher addTargetedDelegate:self priority:1 swallowsTouches:YES];
return self;
}
-(void) onExit {
[[CCDirector sharedDirector].touchDispatcher removeDelegate:self];
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
if (CGRectContainsPoint([self boundingBox], location] {
// some code here
}
}
-(void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event { // code here }
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event { // code here}
-(void)ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event { // code here}
Thanks to Prototypical the issue was solved,
Although the CCTurtle was a layer with a type of sprite on it was nested sprites
which meant that cocos2d had problems creating the correct bounding box for my
"contains touch method"
so with a bit of magic he combined his "get full bounding box" method to the contains touch method to account for the children of the sprite, and the code now relies on collision detection to process the touch.
currently im happily working away on some nice icons for him in return
but wanted to say thank-you to all that helped, and hope that this method comes in handy for anyone with the same problem!
Related
Alright, my mistake, I had previously believed that my ivar was not changing correctly. I was wrong (and got a few downvotes for it). Sorry, the real issue is that i've been calling it incorrectly. I am now posting all the code and i'll let you look at it.
My First class:
classOne.h:
#interface DetailPageController : UIPageViewController
{
BOOL isChromeHidden_;
}
- (void)toggleChromeDisplay;
#end
classOne.m:
#interface DetailPageController ()
#end
#implementation DetailPageController
- (void)toggleChromeDisplay
{
[self toggleChrome:!isChromeHidden_];
}
- (void)toggleChrome:(BOOL)hide
{
//Find chrome value
isChromeHidden_ = !isChromeHidden_;
NSLog(isChromeHidden_ ? #"YES" : #"NO");
}
#end
From the comment's I have received I believe none of that is the actual issue, it's in the following.
classTwo.h: (nothing declared)
classTwo.m:
#interface classTwo ()
#end
#implementation classTwo
//Touches Control
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view]) {
if ([touch tapCount] == 2) {
NSLog(#"double touched");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(toggleChromeDisplay) object:nil];
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view]) {
if ([touch tapCount] == 1) {
NSLog(#"single touch");
[self performSelector:#selector(toggleChromeDisplay) withObject:nil afterDelay:0.5];
}
}
}
- (void)toggleChromeDisplay
{
DetailPageController *pageController = [[DetailPageController alloc] init];
[pageController toggleChromeDisplay];
}
#end
Again, sorry for the previous post, I had thought it was an issue with the method but is in fact the way I am calling it.
What I have been forced to do is implement the touching in the controller which handles the area touched but I have the method for the chrome (nav bar and toolbar) in another.
Overall Question
Why is it that every time I call my toggleChromeDisplay method in classTwo, I always get the same NO from my ivar in classOne?
What i've tried for classTwo.h:
#import "DetailPageController.h"
#interface classTwo : UIViewController
{
DetailPageController *detailPageController_;
}
#property (nonatomic, assign) DetailPageController *detailPageController;
#end
My modified code:
In my classTwo.h:
#import "DetailPageController.h"
#interface PhotoViewController : UIViewController
{
DetailPageController *detailPageController_;
}
#property (nonatomic, strong) DetailPageController *detailPageController;
#end
classTwo.m:
#import "classTwo.h"
#interface classTwo ()
#end
#implementation classTwo
#synthesize detailPageController = detailPageController_;
//Touches Control
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view]) {
if ([touch tapCount] == 2) {
NSLog(#"double touched");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(toggleChromeDisplay) object:nil];
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view]) {
if ([touch tapCount] == 1) {
NSLog(#"single touch");
[self performSelector:#selector(toggleChromeDisplay) withObject:nil afterDelay:0];
}
}
}
- (void)toggleChromeDisplay
{
[self.detailPageController toggleChromeDisplay];
}
#end
classTwo always instantiates a completely brand new instance of DetailPageViewController before calling toggleChromeDisplay on it, and then it allows it to go out of scope and presumably is deallocated. You need to have it use an instance that's actually alive in your program and controls views in the view hierarchy.
You could add a DetailPageViewController property to classTwo and then your -[classTwo toggleChromeDisplay] implementation would look like:
- (void)toggleChromeDisplay
{
[self.detailPageController toggleChromeDisplay];
}
Again, just make sure to assign to that property the instance of the view controller that's actually on screen. alloc and init are used to creating brand new instances of objects, which won't be the ones that already exist in your application if they've been loaded up, for instance, from a storyboard. So, in your application you probably have a DetailPageViewController instance already that is doing things on the screen and controlling interactions with the user - but your classTwo never is able to message it because again it's creating entirely separate instances of that class. So you need to determine where in your application the DetailPageViewController that's visible on screen is getting instantiated, and at that point ensure your classTwo instance can get a reference to it.
Forgive my repetitiveness, but it is a common mistake I see on Stack Overflow. Just make sure that you understand that while there is one definition of a Class, which is where its instance variables and methods are defined, there can be many separate instances of objects that are created from it (we often say they are instantiated, or created, inited, you'll see a number of terms). Each of those objects can have different values for their instance variables (and properties), and they all have their own distinct life time from a memory-management standpoint. Calling the pair of methods alloc and init is one very common way to make a new instance of a class, that has its own lifetime and instance variables.
Finally, I'd like to suggest that you read and follow Apple's Cocoa style guide as your choice of names for methods and classes has caused confusion among your fellow developers. If you start to apply that you will find communication with others to go smoother and your problems easier to understand.
All you need is
- (void)toggleChromeDisplay
{
isChromeHidden_ = !isChromeHidden_;
}
(based on your edit)
toggleChrome: sets the isChromeHidden_ variable, it does not toggle it. To toggle you would write:
- (void)toggleChrome
{
isChromeHidden_ = !isChromeHidden_;
//Find chrome value
NSLog(isChromeHidden_ ? #"YES" : #"NO");
}
!isChromeHidden_ is the opposite value of isChromeHidden_.
I copied your exact code into my project and it is switching between YES and NO as expected. This tells me that your code that you have posted is in fact correct and you are either calling it incorrectly or you are setting the variable somewhere else as well.
Im trying to write a simple program that takes 5 images and allows you to drag them from the bottom of the screen and snap them on to 5 other images on the top in any order you like. I have subclassed the UIImageView with a new class and added the touches began, touches moved and touches ended. Then on my main viewcontroller I place the images and set their class to be the new subclass I created. The movement works great in fact I am NSLogging the coordinates in the custom class.
Problem is I'm trying to figure out how to get that CGpoint info from the touches end out of the custom class and get it to call a function in my main view controller that has the image objects so i can test whether or not the image is over another image. and move it to be centered on the image its over (snap onto it).
here is the code in my custom class m file. MY view controller is basically empty with a xib file with 10 image views 5 of which are set to this class and 5 are normal image views..
#import "MoveImage.h"
CGPoint EndPoint;
#implementation MoveImage
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
startPoint = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint newPoint = [[touches anyObject] locationInView:self.superview];
newPoint.x -= startPoint.x;
newPoint.y -= startPoint.y;
CGRect frm = [self frame];
frm.origin = newPoint;
[self setFrame:frm];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *end = [[event allTouches] anyObject];
EndPoint = [end locationInView:self];
NSLog(#"end ponts x : %f y : %f", EndPoint.x, EndPoint.y);
}
#end
You could write a MoveImageDelegate protocol which your app controller would then implement. This protocol would have methods that your MoveImage class can call to get its information.
In your header file ( if you don't know how to write a protocol )
#protocol MoveImageDelegate <TypeOfObjectsThatCanImplementIt> // usually NSObject
// methods
#end
then you can declare an instance variable of type id, but still have the compiler recognize that that variable responds to your delegate selectors ( so you can code without those annoying warnings ).
Something like:
#interface MoveImage : UIImageView
{
id <MoveImageDelegate> _delegate;
}
#property (assign) id <MoveImageDelegate> delegate;
Lastly:
#implementation MoveImage
#synthesize delegate = _delegate;
And your delegate is complete.
I'm trying to link two gestures one after another. UILongPressGestureRecognizer, then UIPanGestureRecognizer.
I want to detect the Long Press, then allow the Pan gesture to be recognized.
I've Subclassed UIPanGestureRecognizer and Added an panEnabled Bool iVar. In the initWith Frame I've set panEnabled to NO.
In Touches Moved I check to see if it is enabled, and then call Super touchesMoved if it is.
In my LongPress Gesture Handler, I loop though the View's Gestures till I find my Subclassed Gesture and then setPanEnabled to YES.
It seems like it is working, though its like the original pan gesture recognizer is not functioning properly and not setting the Proper states. I know if you Subclass the UIGestureRecognizer, you need to maintain the state yourself, but I would think that if you are subclassing UIPanGestureRecognizer, and for all the touches methods calling the super, that it would be setting the state in there.
Here is my subclass .h File
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface IoUISEListPanGestureRecognizer : UIPanGestureRecognizer {
int IoUISEdebug;
BOOL panEnabled;
}
- (id)initWithTarget:(id)target action:(SEL)action;
#property(nonatomic, assign) int IoUISEdebug;
#property(nonatomic, assign) BOOL panEnabled;
#end
here is the subclass .m File
#import "IoUISEListPanGestureRecognizer.h"
#implementation IoUISEListPanGestureRecognizer
#synthesize IoUISEdebug;
#synthesize panEnabled;
- (id)initWithTarget:(id)target action:(SEL)action {
[super initWithTarget:target action:action];
panEnabled = NO;
return self;
}
- (void)ignoreTouch:(UITouch*)touch forEvent:(UIEvent*)event {
[super ignoreTouch:touch forEvent:event];
}
-(void)reset {
[super reset];
panEnabled = NO;
}
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer {
return [super canPreventGestureRecognizer:preventedGestureRecognizer];
}
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer{
return [super canBePreventedByGestureRecognizer:preventingGestureRecognizer];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
if (panEnabled) {
[super touchesMoved:touches withEvent:event];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesCancelled:touches withEvent:event];
}
#end
If you make a BOOL called canPan and include the following delegate methods you can have both a standard UILongPressGestureRecognizer and UIPanGestureRecognizer attached to the same view. On the selector that gets called when the long press gesture is recognized - change canPan to YES. You might want to disable the long press once it has been recognised and re-enable it when the pan finishes. - Don't forget to assign the delegate properties on the gesture recognisers.
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
if (!canPan && [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
return NO;
}
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
I'm making an iPad project in which a class named "Car" (this is a separate file from the view controller) is supposed to be dragged around the main view.
I setup the class as I saw in an Apple example and I'm able to view my image when I run the application but it's like my class doesn't respond to my touches event and I can't solve the problem.
Here is my class code:
Car.h
#import <UIKit/UIKit.h>
#interface Car : UIView {
UIImageView *firstPieceView;
CGPoint startTouchPosition;
}
-(void)animateFirstTouchAtPoint:(CGPoint)touchPoint forView:(UIImageView *)theView;
-(void)animateView:(UIView *)theView toPosition:(CGPoint) thePosition;
-(void)dispatchFirstTouchAtPoint:(CGPoint)touchPoint forEvent:(UIEvent *)event;
-(void)dispatchTouchEvent:(UIView *)theView toPosition:(CGPoint)position;
-(void)dispatchTouchEndEvent:(UIView *)theView toPosition:(CGPoint)position;
#property (nonatomic, retain) IBOutlet UIImageView *firstPieceView;
#end
and this is my other class code: Car.m
#import "Car.h"
#implementation Car
#synthesize firstPieceView;
#define GROW_ANIMATION_DURATION_SECONDS 0.15 // Determines how fast a piece size grows when it is moved.
#define SHRINK_ANIMATION_DURATION_SECONDS 0.15 // Determines how fast a piece size shrinks when a piece stops moving.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Enumerate through all the touch objects.
NSUInteger touchCount = 0;
for (UITouch *touch in touches) {
// Send to the dispatch method, which will make sure the appropriate subview is acted upon
[self dispatchFirstTouchAtPoint:[touch locationInView:self] forEvent:nil];
touchCount++;
}
}
// Checks to see which view, or views, the point is in and then calls a method to perform the opening animation,
// which makes the piece slightly larger, as if it is being picked up by the user.
-(void)dispatchFirstTouchAtPoint:(CGPoint)touchPoint forEvent:(UIEvent *)event
{
if (CGRectContainsPoint([firstPieceView frame], touchPoint)) {
[self animateFirstTouchAtPoint:touchPoint forView:firstPieceView];
}
}
// Handles the continuation of a touch.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSUInteger touchCount = 0;
// Enumerates through all touch objects
for (UITouch *touch in touches) {
// Send to the dispatch method, which will make sure the appropriate subview is acted upon
[self dispatchTouchEvent:[touch view] toPosition:[touch locationInView:self]];
touchCount++;
}
}
// Checks to see which view, or views, the point is in and then sets the center of each moved view to the new postion.
// If views are directly on top of each other, they move together.
-(void)dispatchTouchEvent:(UIView *)theView toPosition:(CGPoint)position
{
// Check to see which view, or views, the point is in and then move to that position.
if (CGRectContainsPoint([firstPieceView frame], position)) {
firstPieceView.center = position;
}
}
// Handles the end of a touch event.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// Enumerates through all touch object
for (UITouch *touch in touches) {
// Sends to the dispatch method, which will make sure the appropriate subview is acted upon
[self dispatchTouchEndEvent:[touch view] toPosition:[touch locationInView:self]];
}
}
// Checks to see which view, or views, the point is in and then calls a method to perform the closing animation,
// which is to return the piece to its original size, as if it is being put down by the user.
-(void)dispatchTouchEndEvent:(UIView *)theView toPosition:(CGPoint)position
{
// Check to see which view, or views, the point is in and then animate to that position.
if (CGRectContainsPoint([firstPieceView frame], position)) {
[self animateView:firstPieceView toPosition: position];
}
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
// Enumerates through all touch object
for (UITouch *touch in touches) {
// Sends to the dispatch method, which will make sure the appropriate subview is acted upon
[self dispatchTouchEndEvent:[touch view] toPosition:[touch locationInView:self]];
}
}
#pragma mark -
#pragma mark === Animating subviews ===
#pragma mark
// Scales up a view slightly which makes the piece slightly larger, as if it is being picked up by the user.
-(void)animateFirstTouchAtPoint:(CGPoint)touchPoint forView:(UIImageView *)theView
{
// Pulse the view by scaling up, then move the view to under the finger.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:GROW_ANIMATION_DURATION_SECONDS];
theView.transform = CGAffineTransformMakeScale(1.2, 1.2);
[UIView commitAnimations];
}
// Scales down the view and moves it to the new position.
-(void)animateView:(UIView *)theView toPosition:(CGPoint)thePosition
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:SHRINK_ANIMATION_DURATION_SECONDS];
// Set the center to the final postion
theView.center = thePosition;
// Set the transform back to the identity, thus undoing the previous scaling effect.
theView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIImage *img = [ UIImage imageNamed: #"CyanSquare.png" ];
firstPieceView = [[UIImageView alloc] initWithImage: img];
//[img release];
[super addSubview:firstPieceView];
[firstPieceView release];
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
- (void)dealloc
{
[firstPieceView release];
[super dealloc];
}
#end
And here is my code for the view controller: (ParkingviewController.h)
#import <UIKit/UIKit.h>
#import "Car.h"
#interface ParkingViewController : UIViewController {
}
#end
and last but not least the ParkingViewController.m
#import "ParkingViewController.h"
#implementation ParkingViewController
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
Car *car1 = [[Car alloc] init];
[self.view addSubview:car1];
[car1 release];
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
#end
Please forgive me if I've posted all the code but I want to be clear in every aspect of my project so that anyone can have the whole situation to be clear.
You need to set a frame for Car object that you are creating for touches to be processed. You are able to see the image as the clipsToBounds property of the view is set to NO by default.
I have a UIPickerView that gets faded out to 20% alpha when not in use. I want the user to be able to touch the picker and have it fade back in.
I can get it to work if I put a touchesBegan method on the main View, but this only works when the user touches the View. I tried sub-classing UIPickerView and having a touchesBegan in there, but it didn't work.
I'm guessing it's something to do with the Responder chain, but can't seem to work it out.
I've been searching for a solution to this problem for over a week. I'm answering you even if you're question is over a year old hoping this helps others.
Sorry if my language is not very technical, but I'm pretty new to Objective-C and iPhone development.
Subclassing UIpickerView is the right way to do it. But you've to override the - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event method. This is the method called whenever you touch the screen and it returns the view that will react to the touch. In other words the view whose touchesBegan:withEvent: method will be called.
The UIPickerView has 9 subviews! In the UIPickerView class implementation - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event won't return self (this means the touchesBegan:withEvent: you write in the subclass won't be called) but will return a subview, exactly the view at index 4 (an undocumented subclass called UIPickerTable).
The trick is to make the - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event method to return self so you have control over the touchesBegan:withEvent:, touchesMoved:withEvent: and touchesEnded:withEvent: methods.
In these methods, in order to keep the standard functionalities of the UIPickerView, you MUST remember to call them again but on the UIPickerTable subview.
I hope this makes sense. I can't write code now, as soon as I'm at home I will edit this answer and add some code.
Here is some code that does what you want:
#interface TouchDetectionView : UIPickerView {
}
- (UIView *)getNextResponderView:(NSSet *)touches withEvent:(UIEvent *)event;
#end
#implementation TouchDetectionView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesCancelled:touches withEvent:event];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return self;
}
- (UIView *)getNextResponderView:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
UIView * hitTestView = [super hitTest:point withEvent:event];
return ( hitTestView == self ) ? nil : hitTestView;
}
Both of the above answers were very helpful, but I have a UIPickerView nested within a UIScrollView. I'm also doing continual rendering elsewhere on-screen while the GUI is present. The problem is that the UIPickerView doesn't update fully when: a non-selected row is tapped, the picker is moved so that two rows straddle the selection area, or a row is dragged but the finger slides outside of the UIPickerView. Then it's not until the UIScrollView is moved that the picker instantly updates. This result is ugly.
The problem's cause: my continual rendering was keeping the UIPickerView's animation from getting the CPU cycles it needed to finish, hence to show the correct, current selection. My solution --which works-- was this: in the UIPickerView's touchesEnded:withEvent:, execute something to pause my rendering for a short while. Here's the code:
#import "SubUIPickerView.h"
#implementation SubUIPickerView
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
[pickerTable touchesBegan:touches withEvent:event];
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
[pickerTable touchesMoved:touches withEvent:event];
}
- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
[singleton set_secondsPauseRendering:0.5f]; // <-- my code to pause rendering
[pickerTable touchesEnded:touches withEvent:event];
}
- (void) touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
[pickerTable touchesCancelled:touches withEvent:event];
}
- (UIView*) hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
if (CGRectContainsPoint(self.bounds, point))
{
if (pickerTable == nil)
{
int nSubviews = self.subviews.count;
for (int i = 0; i < nSubviews; ++i)
{
UIView* view = (UIView*) [self.subviews objectAtIndex:i];
if ([view isKindOfClass:NSClassFromString(#"UIPickerTable")])
{
pickerTable = (UIPickerTable*) view;
break;
}
}
}
return self; // i.e., *WE* will respond to the hit and pass it to UIPickerTable, above.
}
return [super hitTest:point withEvent:event];
}
#end
and then the header, SubUIPickerView.h:
#class UIPickerTable;
#interface SubUIPickerView : UIPickerView
{
UIPickerTable* pickerTable;
}
#end
Like I said, this works. Rendering pauses for an additional 1/2 second (it already pauses when you slide the UIScrollView) allowing the UIPickerView animation to finish. Using NSClassFromString() means you're not using any undocumented APIs. Messing with the Responder chain was not necessary. Thanks to checcco and Tylerc230 for helping me come up with my own solution!
Set canCancelContentTouches and delaysContentTouches of parent view to NO, that worked for me
Couldn't find a recent, easier solution to this so I rigged something to solve my problem. Created an invisible button that stretches over the picker view. Connected that button with a "Touch Down" recognizer to my parent UIView. Now any functionality can be added, I happened to need a random selection timer to be invalidated when someone touches the picker view. Not elegant but it works.