UPDATE 2
The question in its current form has been answered and accepted even though a problem remains viz. the code now compiles with 0 warnings but still remains unresponsive to gestures. Code will be updated once the problem is solved, hopefully, through the process of preparing a new question. A new question has been posted here.
As far as I know this question has never been asked or answered in any post I have read (see possible duplicates below).
I am trying to write Objective C test code to send either long press, tap or pan messages from any area defined by a rectangular UIView
on an iPhone screen. So far I can send discrete or continuous messages reliably using either UILongPressGesture,
UITapGesture
or UIPanGesture
if both UIView
and handler methods are located in the UIViewController
.
Ultimately I want to send gesture messages from several subclassed UIViews
to common handlers in the UIViewController
but I hit problems when I tried to do it. While writing the code I studied an Apple document on using delegates for UIGestureRecognition together with Objective C methods in Touches (see Note below) and examples found in SO posts (Links below).
The code fails to respond to gestural input. Initially it compiled with four warnings and two issues:
- duplicate protocol
Duplicate protocol definition of 'UIGestureRecognizerDelegate' is ignored
UPDATE 1
This has now been addressed. Paul’s answer clarifies a point I hadn’t understood until now. In response I changed the name of the protocol, tested the code and updated it in this post.
In OneOfSeveralSubviews.h
the protocol is now called FirstGestureRecognizerDelegate.
Relevant changes have been made in the ViewController
e.g.
@interface ViewController : UIViewController <FirstSubviewGestureDelegate, SecondSubviewGestureDelegate>
- undeclared selector.
Three warnings were silenced by making the delegate property typed - i.e. inserting id
(as recommended here) - with similar changes made to the three gesture declarations - i.e. initWithTarget:(id)self
- with a delegate property added to each instance of self
so messages are sent to intended targets - i.e. (id)self.delegate
instead of (id)self
The code previously compiled with one warning but remained unresponsive to gestures.
UPDATE 2
The code now compiles with 0 warnings but still remains unresponsive to gestures.
I checked the code against the four points listed here (i.e. userInteractionEnabled
, minimumPressDuration
, numberOfTapsRequired
and numberOfTouchesRequired
) and made sure to specify the delegate in UIViewController.h.
The first of my initial questions has already been answered
Q.1 how is the protocol definition in the interface a duplicate
?
These two questions have yet to be answered
Q.2 if the UIGestureRecognizer protocol is already there by default, then how do I define the targets that will handle the gesture messages ? or
Q.3 how do I define a delegate target that is not self
but rather a method within the ViewController ?
I'm confident someone with more experience can recognise the answers and explain what I haven't done or suggest questions that shed more light on the problem. Thanks in anticipation.
An illustrated explanation would also benefit those working in Objective C who need to learn more about delegates - old knowledge for some but still a frontier for hacks (like me :-) trying to catch up.
Greg
UPDATE 2
If the .delegate
property is removed from the handleTap:
gesture declaration and the red square is tapped, the program will crash with the following log:
[OneOfSeveralSubviews handleTapSelect:] : unrecognized selector sent to instance 0x7fbe27d064b0
A similar crash log appears if the .delegate
property is also removed from the handleLongPress:
or handlePan:
gesture declaration.
The first part of the code is the ViewController (below) which contains the three target methods called (1) handleLongPress:
(2) handleTapSelect:
and (3) handlePan:
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
OneOfSeveralSubviews *oneForAll=[[OneOfSeveralSubviews alloc] initView:[UIScreen mainScreen].bounds];
[self.view addSubview:oneForAll];
}
(method 1)
- (void)handleLongPress:(UILongPressGestureRecognizer*)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
NSLog(@"started");
self.timer=[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(stillPressing:)
userInfo:nil
repeats:YES];
} else if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
{
[self.timer invalidate];
self.timer = nil;
NSLog(@"ended");
}
}
- (void)stillPressing:(NSTimer *)timer
{
NSLog(@"pressing");
}
(method 2)
- (void)handleTapSelect:(id)sender
{
NSLog(@"tapped");
}
(method 3)
- (void)handlePan:(UIPanGestureRecognizer*)gestureRecognizer
{
[self adjustAnchorPointForGestureRecognizer:gestureRecognizer];
UIView *piece = [gestureRecognizer view];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGPoint translation = [gestureRecognizer translationInView:[piece superview]];
[piece setCenter:CGPointMake([piece center].x + translation.x, [piece center].y + translation.y)];
[gestureRecognizer setTranslation:CGPointZero inView:[piece superview]];
NSLog(@"%f %f", translation.x, translation.y);
[UIView animateWithDuration:0.75
delay:0
usingSpringWithDamping:0.75
initialSpringVelocity:0
options:UIViewAnimationOptionCurveLinear
animations:^{
piece.frame = touchFrame;
}
completion:^(BOOL finished) {
}];
}
}
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
UIView *piece = gestureRecognizer.view;
CGPoint locationInView = [gestureRecognizer locationInView:piece];
CGPoint locationInSuperview = [gestureRecognizer locationInView:piece.superview];
piece.layer.anchorPoint = CGPointMake(locationInView.x / piece.bounds.size.width, locationInView.y / piece.bounds.size.height);
piece.center = locationInSuperview;
}
}
ViewController.h
#import <UIKit/UIKit.h>
#import "OneOfSeveralSubviews.h"
@interface ViewController : UIViewController <FirstSubviewGestureDelegate, SecondSubviewGestureDelegate>
{
CGRect touchFrame;
}
@property (strong, nonatomic) NSTimer *timer;
@end
OneOfSeveralSubviews.m
#import "OneOfSeveralSubviews.h"
@implementation OneOfSeveralSubviews
- (id)initView:(CGRect)rect
{
self = [super initWithFrame:rect];
if (self)
{
CGFloat width = [UIScreen mainScreen].bounds.size.width;
CGFloat height = [UIScreen mainScreen].bounds.size.height;
CGPoint screenCentre = CGPointMake(width*0.5, height*0.5);
CGFloat fullSide = width * 0.33;
CGFloat halfSide = fullSide * 0.5;
touchFrame = CGRectMake(screenCentre.x - halfSide, screenCentre.y - halfSide, fullSide, fullSide);
UIView *hotspot = [[UIView alloc] initWithFrame:touchFrame];
hotspot.backgroundColor = [UIColor redColor];
UILongPressGestureRecognizer *longPressGR = [[UILongPressGestureRecognizer alloc] initWithTarget:(id)self.delegate action:@selector(handleLongPress:)];
[longPressGR setDelegate:(id)self.delegate];
[longPressGR setMinimumPressDuration:0.6f];
[longPressGR setNumberOfTapsRequired:1];
[longPressGR setNumberOfTouchesRequired:1];
[hotspot addGestureRecognizer:longPressGR];
UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:(id)self.delegate action:@selector(handleTapSelect:)];
[tapGR setDelegate:(id)self.delegate];
[tapGR setNumberOfTapsRequired:1];
[tapGR setNumberOfTouchesRequired:1];
[hotspot addGestureRecognizer:tapGR];
[self addSubview:hotspot];
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:(id)self.delegate action:@selector(handlePan:)];
[panGR setDelegate:(id)self.delegate];
[panGR setMinimumNumberOfTouches:1];
[panGR setMaximumNumberOfTouches:1];
[hotspot addGestureRecognizer:panGR];
self.userInteractionEnabled = YES;
}
return self;
}
@end
OneOfSeveralSubviews.h
#import <UIKit/UIKit.h>
@protocol FirstSubviewGestureDelegate <NSObject>
- (void)handleLongPress:(UILongPressGestureRecognizer*)gestureRecognizer;
- (void)handleTapSelect:(UITapGestureRecognizer*)gestureRecognizer;
- (void)handlePan:(UIPanGestureRecognizer*)gestureRecognizer;
@end
@interface OneOfSeveralSubviews : UIView
{
CGRect touchFrame;
}
- (id)initView:(CGRect)rect;
@property (assign) id<FirstSubviewGestureDelegate> delegate;
@end
Links
- What is meant by .delegate=self?
- What are the differences between delegates and events ?
- Why are Objective-C delegates usually given the property assign instead of retain?
- when to use respondsToSelector in objective-c
- Objective C responds to selector
- iOS - UILongPressGestureRecognizer
- Using UILongPressGestureRecognizer and UISwipeGestureRecognizer
- Using UILongPressGestureRecognizer For Subviews of UIScrollview
possible duplicates
- Duplicate protocol definition warning, but I need multiples of this
protocol - warning: duplicate protocol definition of '…' is ignored
- Duplicate protocol definition
Note
The Apple document on handling gestures includes examples in Swift. If you work in Objective C refer to Touches but bear in mind that it no longer compiles on versions “earlier than iOS 7”. If you are able to provide a more useful reference for Objective C programmers please edit this note.
MyGestureRecognizerDelegate
has removed the conflict but the problem remains. There were no other conflicts. – Oke