I am creating an app with cards. In my mainViewController, I have this code:
CardView *cardView = [[CardView alloc] initWithFrame:CGRectMake(0, 0, CardWidth, CardHeight)];
cardView.card = [player.closedCards cardAtIndex:t];
[self.cardContainerView addSubview:cardView];
[cardView animateDealingToBottomPlayer:player withIndex:t withDelay:delay];
delay += 0.1f;
where CardView is a subclass of UIView. Each Card is a unique cardView and in CardView.m I do have:
#implementation CardView
{
UIImageView *_backImageView;
UIImageView *_frontImageView;
CGFloat _angle;
}
#synthesize card = _card;
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
self.backgroundColor = [UIColor clearColor];
[self loadBack];
self.userInteractionEnabled=YES;
}
return self;
}
- (void)loadBack
{
if (_backImageView == nil)
{
_backImageView = [[UIImageView alloc] initWithFrame:self.bounds];
_backImageView.image = [UIImage imageNamed:#"Back"];
_backImageView.contentMode = UIViewContentModeScaleToFill;
[self addSubview:_backImageView];
}
}
and the implementations for other functions .
Since in order to win space, one card is placed on top of the others (half of teh card is visible and the rest is covered by the next card and so on), I want to identify touches on each card.
If I place that code in CardView:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Card is touched");
}
it is never called. If I place it in the GameView Controller it is called anywhere that I will touch, but I do not know how to identify which cardView is called. Can you give me a hint?
EDIT:
I decided to use gestures. Therefore in my mainViewController changed the code to this:
for (PlayerPosition p = startingPlayer.position; p < startingPlayer.position + 4; ++p)
{
CardView *cardView = [[CardView alloc] initWithFrame:CGRectMake(0, 0, CardWidth, CardHeight)];
cardView.card = [player.closedCards cardAtIndex:t];
cardView.userInteractionEnabled=YES;
[self.cardContainerView addSubview:cardView];
[cardView animateDealingToBottomPlayer:player withIndex:t withDelay:delay];
delay += 0.1f;
UITapGestureRecognizer *recognizer=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(cardSelected:)];
[cardView addGestureRecognizer:recognizer];
}
but this is never called.
-(void)cardSelected:(UITapGestureRecognizer *)recognizer
{
NSLog(#"Card Selected with gestures");
}
Why is that?
EDIT 2:
I have also tried adding this:
self.cardContainerView.userInteractionEnabled=YES;
or changing this:
UITapGestureRecognizer *recognizer=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(cardSelected:)];
to this:
UITapGestureRecognizer *recognizer=[[UITapGestureRecognizer alloc]initWithTarget:self .cardContainerView action:#selector(cardSelected:)];
but none of them worked.
If you instead add a tap gesture recogniser to each card view you can get a callback to a method you specify and the gesture is connected to the view so you can directly get a reference to it.
Your CardView (which I guess is a subclass of UIView?) has 2 subviews, which are image views:
UIImageView *_backImageView;
UIImageView *_frontImageView;
You may want to set userInteractionEnabled to YES on one or both of them (depending on when the card should be tappable and when the subviews are shown and hidden).
You can also act as the delegate of the gesture (if you need to, or just temporarily to debug that the gesture is getting triggered but blocked by something other gesture).
Related
I have started using this great SVGKit for iOS. I am using a subclass of SVGKImageView. Now before subclassing SVGKImageView i have been easily able to add a UITapGestureRecognizer to it. But my requirement made me to use subclasses as i have to place tens of SVGKImageViewon to my parent view.
(This is because there would be SVGKImageViewtransparent portions on other views and i want to be able to ignore touches on the views areas where alpha = 0 so i want to be able to detect touch on a single SVGKImageView then check for alpha value and if touch is on transparent area forward the touch event to the next view and so on until non transparent area of some view found)
Now after subclassing the SVGKImageViewadding UITapGestureRecognizer does not work, also this
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
is not called in my subclass. I have also set SVGKImageView's userInteractionEnabled to YES but of no avail.
Can anyone help tell me that why there is no touch/tap event being passed to my
SVGKImageView subclass?
Below is my subclass init method
- (instancetype)init{
self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(svgImageTapped:)];
self.tapGestureRecognizer.numberOfTapsRequired = 1;
self.tapGestureRecognizer.delegate = self;
[self addGestureRecognizer:self.tapGestureRecognizer];
self.imageSVG = [SVGKImage imageNamed:#"SomeFile.svg"];
self = (Subclass*)[[SVGKLayeredImageView alloc] initWithSVGKImage:self.imageSVG];
[self sizeToFit];
self.userInteractionEnabled = YES;
self = [super init];
return self;
}
Let order your code:
- (instancetype)init{
self = [super init]; // <--- should go first
self = (Subclass*)[[SVGKLayeredImageView alloc] initWithSVGKImage:[SVGKImage imageNamed:#"SomeFile.svg"]];
// self.imageSVG = [SVGKImage imageNamed:#"SomeFile.svg"];
[self sizeToFit];
self.userInteractionEnabled = YES;
return self;
}
I have a UIView with a UITextField, UIButton and UITable view. The textfield and button compromise a search bar and the results are then loaded into the table view.
I'd like to make it so they keyboard dismisses. If the user taps something when they are editing the text field. My strategy would be to add a gesture recognizer to the UIView, but then gesture recognizer seems to intercept all the touches from the table view and you |tableView:didSelectCellAtIndexPath:| never gets called. Whats interesting (to me at least) is that the UIButton is still tap-able when the user is editing the field even though the UITableView isn't.
I've tried implementing |gestureRecognizer::shouldRecognizeSimultaneouslyWithGestureRecognizer:| to alway return yes, but that doesn't help. I've also tried setting
singleTapRecognizer.cancelsTouchesInView = NO;
which also doesn't help.
I'm happy with the idea of adding and removing the gesture recognizer when the text field calls |textFieldDidBeginEditing:| and |textFieldDidFinishEditing:|, though this feels messy and it still takes two taps to touch a cell when you're editing the text field (one to dismiss they keyboard and remove the recognizer, and one to tap the cell).
Is there a better way?
Relevant code below:
- (void)loadView {
[super loadView];
self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.scrollView.backgroundColor = [FDEColors viewBackgroundColor];
self.view = self.scrollView;
self.searchField = [[UITextField alloc] initWithFrame:CGRectZero];
self.searchField.placeholder = #"What are you looking for?";
self.searchField.backgroundColor = [FDEColors textFieldBackgroundColor];
self.searchField.clipsToBounds = YES;
self.searchField.layer.borderColor = [[FDEColors buttonColor] CGColor];
self.searchField.layer.borderWidth = 1.f;
self.searchField.returnKeyType = UIReturnKeySearch;
self.searchField.delegate = self;
[self.view addSubview:self.searchField];
self.searchButton = [[UIButton alloc] initWithFrame:CGRectZero];
self.searchButton.backgroundColor = [FDEColors buttonColor];
[self.searchButton setTitle:#"Search" forState:UIControlStateNormal];
[self.searchButton addTarget:self
action:#selector(searchPressed:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.searchButton];
self.resultsTableView =
[[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
self.resultsTableView.delegate = self;
self.resultsTableView.dataSource = self;
self.resultsTableView.backgroundColor = [FDEColors viewBackgroundColor];
[self.resultsTableView setSeparatorInset:UIEdgeInsetsZero];
self.resultsTableView.layoutMargins = UIEdgeInsetsZero;
[self.resultsTableView registerClass:[FDESearchResultsCell class]
forCellReuseIdentifier:[FDESearchResultsCell reuseIdentifier]];
[self.view addSubview:self.resultsTableView];
self.singleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:self.singleTapRecognizer];
}
- (void)dismissKeyboard {
[[self view] endEditing:YES];
}
Try implementing the gesture recognizer's delegate method:
- (BOOL) gestureRecognizer:ShouldReceiveRouch:
In this method, check for the touch's location. If it's inside the tableview, return no, so the tableview can receive the touch. Otherwise, return YES and let the recognizer handle the touch.
Edit: As for the button receiving the touch despite the recognizer's existence, as of iOS6 Apple decided to give buttons and some other controls priority when it comes to recognizing gestures. It only applies to antagonizing gestures though, in your case a single tap. If for example you also had a pan recognizer, the recognizer would have precedence, not the button.
Edit 2:
An example implementation of the method mentioned above:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Determine if the touch is inside the custom subview
if ([touch view] == self.yourTableView){
// If it is, prevent all of the delegate's gesture recognizers
// from receiving the touch
return NO;
}
return YES;
}
In my project I would like to make a hidden image view appear when it is tapped for more than 3 seconds. I know I need to use NSTimer, but I have never created a UIImageView touch event. How can I combine the TapGestureRecognizer with NSTimer to achieve what I want to do? I am completely new to touch events in iOS, and I am just beginning to explore this. So, any help would be appreciated. Thank you!
UPDATE:
I implemented the UILongPressGestureRecognizer as below, but now, the hidden image appears even if I press somewhere outside of the image. How can I make it appear only if pressing the hidden image itself?
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(handleLongPress:)];
longPress.numberOfTouchesRequired = 1;
longPress.minimumPressDuration = 3;
[self.view addGestureRecognizer:longPress];
}
-(void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
BrokenGlass.hidden = NO;
}
}
You don't want a UITapGestureRecognizer and a timer, you want a UILongPressGestureRecognizer.
The image is appearing because you are using the gesture recognizer in the entire view.
[**self.view** addGestureRecognizer:longPress];
The gesture will not trigger on a hidden element.
Here is my solution:
#interface ViewController () <UIGestureRecognizerDelegate>
#property (nonatomic, strong) UIImageView *imageView;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// create the imageView
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
// enable the user interaction in the imageView (otherwise it will not receive events)
_imageView.userInteractionEnabled = YES;
// add as a subview of the main view
[self.view addSubview:_imageView];
// create the gesture recognizer
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressHandler:)];
longPressGesture.delegate = self;
longPressGesture.minimumPressDuration = 3;
// add the gesture to the imageView
[_imageView addGestureRecognizer:longPressGesture];
}
#pragma mark - UIGestureRecognizerDelegate
- (void)longPressHandler:(UILongPressGestureRecognizer *)gestureRecognizer {
// show the image
_imageView.image = [UIImage imageNamed:#"cat.jpeg"];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
container = [[UIView alloc] initWithFrame:self.view.frame];
UIImageView* main_im0 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon1.png"]];
[container addSubview:main_im0];
[self.view addSubview:container];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *tt = [touches anyObject];
UIImageView *touchedview=(UIImageView*)[tt view];
NSArray *views = [container subviews];
for (UIImageView* im in views)
{
UIImageView* focus=im;
if (touchedview==focus)
{
}
}
}
I have this piece of code that setting up an ImageView called main_im0 which is put into a UIView container which then put into view. When I clciked on the main_im0, I expect the touch function would hit the condition of touchedview==focus. However, I couldn't get that condition activated? what's wrong?
Please enable
main_im0.userInteractionEnabled = YES;
by default it's No for UIImageView
Jason, maybe I am misinterpreting your code but aren't you going a round about way of achieving a tap gesture on a UIView?
You can use:
// enable user interaction with this view
main_img0.userInteractionEnabled = YES;
// setup a tap gesture to call a method once triggered (like a javascript mouseClick event)
UITapGesture *tapGesture = [[UITapGesture alloc] initWithTarget:self selector:#selector(myMethodToDoSomething)];
[main_img0 addGestureRecognizer:tapGesture];
// if you are not using Automatic Reference Counting, then remember to release your allocated tapGesture object
[tapGesture release];
...somewhere later in your code....
// method to do something
-(void)myMethodToDoSomething
{
NSLog(#"This method executed");
}
Now when you tap on your main_img0 view, the method "myMethodToDoSomething" will execute
By the way, if you just want a custom looking button with your own photoshop designed image, you can simply create a UIButton with code and set the background image property of the UIButton. Like so:
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
[button setImage:[UIImage imageNamed:#"mybutton.png"] forControlState:UIControlStateNormal];
I have created a UIViewController, which contains a UIView object.
I want the UIView object response to a single tap by 1 finger, and also pinch by 2 fingers.
I have clicked the "User Interaction Enabled" and the "Multiple touch" options in the xib.
I create both UITapGestureRecognizer, and UIPinchGestureRecognizer inside the UIView function initWithFrame:, as it is the only entry point for the UIView object. But the object doesn't response to the UIGestureRecognizer objects. Why? Or, where is the best place to put the UIGestureRecognizer objects?
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
// single tap
UITapGestureRecognizer* singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[self addGestureRecognizer: singleTap];
// 2 fingers pinch
UIPinchGestureRecognizer* doubleMove = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handleDoubleMove:)];
[self addGestureRecognizer: doubleMove];
}
return self;
}
Your problem is that when you instantiate your custom view subclass in IB, initWithCoder: is called. If you had done it programmatically using initWithFrame: it would have worked properly.
Two ways you can fix this,
Move the gestures setup to a different method and call them in both initWithFrame: and initWithCoder:. I would recommend doing this if you intend to reuse this component and expose some kind of callbacks on gestures.
If you want to implement this once and have a lot of interacting with the controller element, add them in viewDidLoad.
I tried your code and it works for me.
Where are you setting the view? Maybe it has any other view in front, or its superview has userInteractionEnabled disabled.
I created a UIView subclass and added this code:
-(void) handleSingleTap:(UITapGestureRecognizer *)gr {
NSLog(#"handleSingleTap");
}
-(void) handleDoubleMove:(UIPinchGestureRecognizer *)gr {
NSLog(#"handleDoubleMove");
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
// single tap
UITapGestureRecognizer* singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[self addGestureRecognizer: singleTap];
// 2 fingers pinch
UIPinchGestureRecognizer* doubleMove = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handleDoubleMove:)];
[self addGestureRecognizer: doubleMove];
}
return self;
}
Then, in my controller:
-(void) viewDidLoad {
[super viewDidLoad];
MyView * myView = [[MyView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
myView.backgroundColor = [UIColor greenColor];
[self.view addSubview:myView];
[myView release];
}
And it works.
i think if you are working with .xib, initialization can also be done in
-(void)awakeFormNib;
Adding the GestureRecognizers is typically done in the viewDidLoad method of the UIViewController.