if Duplicate protocol definition of 'UIGestureRecognizerDelegate' is ignored how can I define targets
Asked Answered
O

1

2

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 UIViewControllerbut 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:

  1. 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>

  1. 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

possible duplicates

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.

Oke answered 12/3, 2018 at 23:52 Comment(0)
I
3

UIGestureRecognizerDelegate is declared by UIKit. You can't create your own protocol named UIGestureRecognizerDelegate; it is a conflicting declaration.

By convention, classes and protocols prefixed with UI are declared by UIKit and you should not declare your own objects and classes with this prefix. Use something like MyGestureRecognizerDelegate

Interjoin answered 13/3, 2018 at 0:52 Comment(5)
Paul, the change to MyGestureRecognizerDelegate has removed the conflict but the problem remains. There were no other conflicts.Oke
You need to set some object that implements MyGestureRecognizerDelegate as the delegate of your instance of OneOfSeveralSubviews and then call the relevant delegate method from the appropriate gesture recogniser action method.Interjoin
You need to have an action method in the relevant UIView subclass and have that action method invoke [self.delegate handleTap:guestureRecognizer];.Interjoin
I’m afraid I don’t understand this comment. Could you suggest a link to an example that might illustrate what you meant ?Oke
Paul, on the face of it, and in fairness, this has to be the accepted answer to my question, even though there are still problems. I'll work on these in the same way and prepare a new question if necessary. Your help was greatly appreciated.Oke

© 2022 - 2024 — McMap. All rights reserved.