Implementing UITapGestureRecognizer directly into UIView instead of ViewController does not work - objective-c

As explained in this SO-answer I am trying to implement UITapGestureRecognizer in a subclass of UIIMageView. My approach can be found below.
An instance of MenuButtonImageView is a subview of homeScreenViewController.view.
I implemented touchesBegan:withEvent:, touchesMoved:withEvent: and touchesEnded:withEvent: too and do work fine.
Problem: Somehow handleSingleFingeredTap: is never called. What could be the problem with this?
#interface MenuButtonImageView : UIImageView
#property (nonatomic, weak) IBOutlet HomescreenViewController *homeScreenViewController;
#property (nonatomic, readwrite) Visibility visibility;
- (void)handleSingleFingeredTap:(UITapGestureRecognizer *)recognizer;
#end
#implementation MenuButtonImageView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.visibility = HIDDEN;
self.userInteractionEnabled = YES;
UITapGestureRecognizer * singleFingeredTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleFingeredTap:)];
singleFingeredTap.numberOfTapsRequired = 1;
[self addGestureRecognizer:singleFingeredTap];
}
return self;
}
- (void)handleSingleFingeredTap:(UITapGestureRecognizer *)recognizer {
NSLog(#"handleTap was called"); // This method is not called!
[self toggleMainMenu];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { ... }
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { ... }
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { ... }
#end

You add the recognizer in initWithFrame method. Check if your view is initialized this way. It may be created with another constructor like initWithCoder, and then your code is not executed. Put a breakpoint inside initWithFrame method to find out.
You can put the code in a separate method and call it from both constructors. It depends on how you use it. The initWithFrame is used quite common when you create a view from code.

Related

How to get canDisplayBannerAds producing viewDidLayoutSubview callbacks?

In my code, when I set canDisplayBannerAds=YES on my view controller, I get callbacks to viewDidLayoutSubviews when the ad disappears but not when the ad appears. I'm guessing this is because Apple moves the original self.view of the view controller to self.originalContentView when you set canDisplayBannerAds to YES.
My question is, what is a reasonable work around for this?
My solution to this problem is to replace self.view before setting canDisplayBannerAds=YES with a UIView that overrides layoutSubviews.
#protocol LayoutViewDelegate <NSObject>
- (void) layout;
#end
#interface LayoutView : UIView
#property (nonatomic, weak) id<LayoutViewDelegate> delegate;
#end
#implementation LayoutView
- (void) layoutSubviews {
[super layoutSubviews];
if (self.delegate) [self.delegate layout];
}
#end
I do this replacement in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Calendar.viewDidLoad");
// Replace the view, before setting up ads for iOS7, so we can get callbacks for viewDidLayoutSubviews; otherwise, we only get viewDidLayoutSubviews callbacks when ad disappears.
if ([Utilities ios7OrLater]) {
self.layoutView = [[LayoutView alloc] initWithFrame:self.view.frame];
self.view = self.layoutView;
self.layoutView.delegate = self;
}
}
And in viewDidAppear, I do:
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([Utilities ios7OrLater]) {
self.canDisplayBannerAds = YES;
}
}
And I add the delegate method:
// This *always* gets called when the banner ad appears or disappears.
#pragma - LayoutViewDelegate method
- (void) layout {
// do useful stuff here
}
#pragma -

delegate method not getting called

I have looked at all the other questions with the same problem but I cannot seem to get my heard around it. I am pretty sure I have done everything correctly as this is not my first time using delegates.
//PDFView.h
#class PDFView;
#protocol PDFViewDelegate <NSObject>
-(void)trialwithPOints:(PDFView*)pdfview;
#end
#interface PDFView : UIView
#property (nonatomic, weak) id <PDFViewDelegate> delegate;
In the implementation file i am trying to call the delegate method from touchesMoved delegate of view
//PDFView.m
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.delegate trialwithPOints:self];
}
The class where the delegate method is implemented
//points.h
#import "PDFView.h"
#interface points : NSObject <PDFViewDelegate>
//points.m
//this is where the delegate is set
- (id)init
{
if ((self = [super init]))
{
pdfView = [[PDFView alloc]init];
pdfView.delegate = self;
}
return self;
}
-(void)trialwithPOints:(PDFView *)pdf
{
NSLog(#"THE DELEGATE METHOD CALLED TO PASS THE POINTS TO THE CLIENT");
}
So this is how i have written my delegate and somehow the delegate is nil and the delegate method is never called.
At the moment I am not doing anything with the delegate, I just want to see it working.
Any advices on this would be highly appreciated.
I think it is because you did not hold the reference to the instance of the delegate, and it got released because it is declared weak. You might be doing this:
pdfView.delegate = [[points alloc] init];
which you should fix to something like:
_points = [[points alloc] init];
pdfView.delegate = _points;
where _points is instance variable.

UIGestureRecognizer - Get the reference to the touched UIViewController Instead of its View?

How do I get a reference to the UIViewController of a touched view?
I am using a UIPanGestureRecognizer on the view of a UIViewController. Here's how I initialize it:
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc]init];
[[self view]addSubview:[thisTaskController view]];
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
[[thisTaskController view] addGestureRecognizer:panRec];
In the tiggered action triggered using the gesture recognizer I am able to get the view from the parameter using recognizer.view
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
UIView *touchedView = [[UIView alloc]init];
touchedView = (UIView*)[recognizer view];
...
}
However what I really need is the underlying UIViewController of the view touched. How can I get a reference to the UIViewController that contains this view instead of only the UIView?
I would say that it is more a design issue than just getting a reference. So I would follow several simple advises:
Owner should catch events from its view. I.e. TaskUIViewController sould be a target to UIPanGestureRecognizer which you added to its view.
If a controller has a sub-controller and waits from its sub-controller some responses - implement this as delegate.
You have memory leak in your "handlePan:" method.
Here is a skeleton to solve your issue:
#protocol CallbackFromMySubcontroller <NSObject>
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController;
#end
#interface OwnerController : UIViewController <CallbackFromMySubcontroller>
#end
#implementation OwnerController
- (id)init
{
...
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc] init];
...
}
- (void)viewDidLoad
{
...
[self.view addSubview:thisTaskController.view];
...
}
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController
{
NSLog(#"Yahoo. I got an event from my subController's view");
}
#end
#interface TaskUIViewController : UIViewController {
id <CallbackFromMySubcontroller> delegate;
}
#end
#implementation TaskUIViewController
- (id)initWithOwner:(id<CallbackFromMySubcontroller>)owner
{
...
delegate = owner;
...
}
- (void)viewDidLoad
{
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.view addGestureRecognizer:panRec];
[panRec release];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
...
[delegate calbackFromTaskUIViewControllerOnPanGesture:self];
...
}
#end
[touchedView nextResponder] will return the UIViewController object that manages touchedView (if it has one) or touchedView's superview (if it doesn’t have a UIViewController object that manages it).
For more information, see the UIResponder Class Reference. (UIViewController and UIView are subclasses of UIResponder.)
In your case, since you happen to know that touchedView is your viewController's view (and not, for instance, a subview of your viewController's view), you can just use:
TaskUIViewController *touchedController = (TaskUIViewController *)[touchedView nextResponder];
In the more general case, you could work up the responder chain until you find an object of kind UIViewController:
id aNextResponder = [touchedView nextResponder];
while (aNextResponder != nil)
{
if ([aNextResponder isKindOfClass:[UIViewController class]])
{
// we have found the viewController that manages touchedView,
// so we break out of the while loop:
break;
}
else
{
// we have yet to find the managing viewController,
// so we examine the next responder in the responder chain
aNextResponder = [aNextResponder nextResponder];
}
}
// outside the while loop. at this point aNextResponder points to
// touchedView's managing viewController (or nil if it doesn't have one).
UIViewController *eureka = (UIViewController *)aNextResponder;

delegate method does not get called

I'm completely stuck with calling a method from a UIView subclass, the method just doesn't get fired, I have a feeling that I'm doing something wrong but after searching the web I did not find any clue. Thank you in advance
Here's the iPadMainViewController.h file
#import <UIKit/UIKit.h>
#import "TouchView.h"
#interface iPadMainViewController : UIViewController <TouchViewDelegate>
#property (retain) UIWebView *detailsView;
#end
and the iPadMainViewController.h file that holds the method
- (void)MethodNameToCallBack:(NSString *)s
{
NSLog(#"%#",s);
}
Here's the TouchView.h file, which is supposed t
#protocol TouchViewDelegate
- (void)MethodNameToCallBack:(NSString *)s;
#end
#interface TouchView : UIView {
id<TouchViewDelegate> delegate;
}
#property (nonatomic, assign) id delegate;
#end
Here's the TouchView.m file which is supposed to call a method of it's delegate
#implementation TouchView
#synthesize delegate;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"HELLO FROM INSIDE");
[[self delegate] MethodNameToCallBack:(NSString *)#"HELLO FROM OUTSIDE"];
}
#end
Synthesizing the delegate is not enough, because it just creates the getter and the setter methods. It does not create an instance of iPadMainViewController.
So after you create an instance of TouchView, you should assign an instance of iPadMainViewController as the delegate.
iPadMainViewController *controller = [[iPadMainViewController alloc] init...
// ...
TouchView *touchView = [[TouchView alloc] init...
// ...
touchView.delegate = controller;
Or in the iPadMainViewController's viewDidLoad method:
- (void)viewDidLoad
{
// ...
self.touchView.delegate = self;
}
check after you instantiated a TouchView instance, did you assign its delegate?
Enhance your touchesBegan implementation a little for further debugging:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"HELLO FROM INSIDE");
NSLog(#"our delegate is set towards: %#", delegate);
...
}
Does it log something useful in that second logging statement?
I presume it prints nil and that would be the root cause of your issue; you forgot to assign the delegate.

Subclassing UIPanGestureRecognizer to wait for Condition before recognition

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;
}