Getting a crash with "unrecognized selector sent to instance" message, when attaching a UIPanGestureRecognizer to subclassed UIImageView. I need to subclass UIImageView to hold custom properties, so I just did that and added those properties. I have tested panning on UIImageView directly, and it works as expected. However, it doesn't work with subclassed UIImageView. Here's the code (self.myImageView is subclassed UIImageView and self.defaultImageView is UIImageView ):
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer * pgr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveCustomImageView:)];
[self.myImageView addGestureRecognizer:pgr]; //nop, getting error during runtime on start panning
[pgr release];
...
pgr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveUIImageView:)];
[self.defaultImageView addGestureRecognizer:pgr]; //yep, that works
[pgr release];
The handler methods declarations are of the same structure
-(void)moveUIImageView:(UIPanGestureRecognizer *)pan{
...
}
-(void)moveCustomImageView:(UIPanGestureRecognizer *)pan{
...
}
UIImageView by default has setUserInteractionEnabled flag set to NO & setMultipleTouchEnabled set to NO. Did you try turning these flags to YES?
You can change this like so -
[self.myImageView setMultipleTouchEnabled:YES];
[self.myImageView setUserInteractionEnabled:YES];
Related
I'm using UILongPressGestureRecognizer class to handle if one item is being selected.
The logic is as follows: User press during 1 second an item (UIView subclass). Once the gesture is detected, the item is highlighted and moveable.
The user must move this item across the screen without stop touching it.
The problem I'm facing is the gesture recognized shadows touchesBegan/Move/Ended necessary for the item class to arrange the movement.
I tried to remove the gesture recognized once is detected and the item selected. But still sending messages to the handle of gesture instead of call touches methods.
Anyone knows any way to stop "listening" the gesture recognizer without leave the finger of the screen?
Thanks.
Here the code:
-(void)addGestures
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleLongPress:)];
longPress.minimumPressDuration = iItemLongPressTime;
[self addGestureRecognizer:longPress];
[longPress release];
}
- (void)handleLongPress:(UILongPressGestureRecognizer*)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(#"Long press Ended");
}
else {
if (self.isSelected) return;
if ([delegate respondsToSelector:#selector(singleTouch:)])
[delegate singleTouch:self];
[self removeGestureRecognizer:[self.gestureRecognizers objectAtIndex:0]];
NSLog(#"Long press detected.");
}
}
As you can see in the else branch the delegate calls enables all procedures to mark this item as selected, and just after remove the recognizers.
What I'm missing?
--EDIT--
Done! This works:
#pragma mark Gesture Functions
-(void)addGestures
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleLongPress:)];
longPress.minimumPressDuration = iItemLongPressTime;
[self addGestureRecognizer:longPress];
[longPress release];
}
- (void)handleLongPress:(UILongPressGestureRecognizer*)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(#"Long press Ended");
}
else {
NSLog(#"Long press detected.");
if (self.isSelected) return;
if ([delegate respondsToSelector:#selector(singleTouch:)])
[delegate singleTouch:self];
[sender removeTarget:self action:#selector(handleLongPress:)];
sender.enabled = NO;
[self removeGestureRecognizer:sender];
}
}
Regards!
Does the custom UIView class have its own touch handling code? If not, a simple solution is to set the allowableMovement property of the UILongPressGestureRecognizer to CGFLOAT_MAX, or some big number, and use the gesture update callbacks to drag your custom view around. You can get the displacement using the - (CGPoint)locationInView:(UIView *)view method on the superview, and compare its position to when the recognizer began.
There are two solutions in my mind.
For animating uiview, please wrote a new class which is inherited from the UIView class and implement the touch delegates instead of writing the Gustures to handle animation(if the touch delegates are not triggering in the current class).
2.I have successfully removed the UILongPressGestureRecognizer after triggered it once.
Please refer the below code .ask me if you have any queries
Steps I have Done
I have added a UIView as "myView" to my main-view when main-view loads.
I have given the Tag to the myView (you can give 1,2,3…etc) to differentiate the tapped view from the main-view subviews.
Assigned the UILongPressGestureRecognizer gesture to myView and assigned target as "moveMe" method.
When user Pressed the myView long, the "moveMe" method will trigger.
Then I iterated the mainView Subviews with the condition Tag == 1
I have removed the UILongPressGestureRecognizer from the subview.As we can know that Tagged 1 main-view subView is myView.
So the NSLog(#"gesture removed"); and NSLog(#"moveMe"); will log in console only at one time.
The NSLog(#"touchesBegan"); will trigger first instead of triggering the "moveMe" method.
Then NSLog(#"touchesBegan"); will trigger always after removed the gesture . "moveMe" method will not trigger ever.
Code
- (void)viewDidLoad {
//Adding to UIView to main view when application is loading.
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 80, 80)];
myView.backgroundColor = [UIColor viewFlipsideBackgroundColor];
myView.tag = 1; //adding a tag to identify it.
//Adding Long Press Gesture to the UIView.
UILongPressGestureRecognizer *myGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(moveMe:)];
[myView addGestureRecognizer:myGesture];
[myGesture release];
myGesture = nil;
[self.view addSubview:myView];
[myView release];
myView = nil;
[super viewDidLoad];
}
//Method to trigger when user pressed long on the added UIView.
-(void)moveMe:(id)sender
{
for (UIView *subViews in [self.view subviews])
{
if (subViews.tag == 1) {
[subViews removeGestureRecognizer:sender];
NSLog(#"gesture removed");
}
}
NSLog(#"moveMe");
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesBegan");
}
or please refer Disable gesture recognizer iOS
UILongPressGestureRecognizer *longPressOnUndoGesture = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleLongPressOnUndoGesture:)];
[longPressOnUndoGesture setMinimumPressDuration:2.0];
[longPressOnUndoGesture release];
i have the above code to deasctivate the autoscroll timer in my applicationthis is the function for this.
-(void) handleLongPressOnUndoGesture:(UILongPressGestureRecognizer*)recognizer {
[autoscrollTimer invalidate];
}
but when i taptohold for 2 seconds it wont stop the timer.is there any error in my code for gesture.
Thanks in advance.
You're not using the gesture recognizer, as you release it immediately as you created it. You have to attach it to a view like this:
UILongPressGestureRecognizer *longPressOnUndoGesture = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleLongPressOnUndoGesture:)];
[longPressOnUndoGesture setMinimumPressDuration:2.0];
// TRICK HERE
[self.view addGestureRecognizer:longPressUndoGesture];
[longPressOnUndoGesture release];
It seems to me that you are not adding the gesture recognizer to the view it should work upon:
[self.view addGestureRecognizer: longPressOnUndoGesture];
(if self is your controller).
I'm using the code below to integrate right/left swipe events to a UIImageView object (called photo_view) but it did not work after tests in iphone simulator and device. The methods handleLeftSwipe and handleRightSwipe below are not even called as logging didn't print anything in the debugger logs as it should. Here is the code:
- (void)viewDidLoad
{
[super viewDidLoad];
UISwipeGestureRecognizer *leftSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleLeftSwipe:)];
leftSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
leftSwipeRecognizer.numberOfTouchesRequired = 1;
[photo_view addGestureRecognizer:leftSwipeRecognizer];
leftSwipeRecognizer.delegate = self;
[leftSwipeRecognizer release];
UISwipeGestureRecognizer *rightSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleRightSwipe:)];
rightSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
rightSwipeRecognizer.numberOfTouchesRequired = 1;
[photo_view addGestureRecognizer:rightSwipeRecognizer];
rightSwipeRecognizer.delegate = self;
[rightSwipeRecognizer release];
}
- (void)handleLeftSwipe:(UISwipeGestureRecognizer *)recognizer
{
NSLog(#"handleLeftSwipe called");
}
- (void)handleRightSwipe:(UISwipeGestureRecognizer *)recognizer
{
NSLog(#"handleRightSwipe called");
}
Any idea what the reason ?
Thx in advance for helping,
Stephane
UIImageView instances defaults userInteractionEnabled to NO. Try setting it to YES.
Make Sure you're also including the <UIGestureRecognizerDelegate> in your .h file.
I just trying to get to QLPreviewController.view. Indeed, I want to catch a tap event on its view to show/hide toolbar etc. I am trying:
QLPreviewController* qlpc = [QLPreviewController new];
qlpc.delegate = self;
qlpc.dataSource = self;
qlpc.currentPreviewItemIndex=qlIndex;
[navigator pushViewController:qlpc animated:YES];
qlpc.title = [path lastPathComponent];
[qlpc setToolbarItems:[NSArray arrayWithObjects:self.dirBrowserButton,self.space, self.editButton, self.btnSend, nil] animated:YES];
UITapGestureRecognizer* gestTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showControls:)];
gestTap.cancelsTouchesInView=NO;
[qlpc.view addGestureRecognizer:[gestTap autorelease]];
[qlpc release];
And nothing happens
If I attach UITapRecognizer onto navigationController.view, it fires only if I touch toolbar/navbar. UISwipeGestureRecognizer works fine in that case.
I tried to attach a transparent overlay view and add gesture recognizers on it, but no luck.
Well, I saw some apps that implements such a feature so obviously it is possible, but how?
Sorry, I googled all day long and didn't find any solution. Please, help me.
With your solution, does the QLPreviewController's view still recieve touches? I've tried to do something similar (I'm stealing the view from QLPreviewController to use it) and it looks like my overlay view doesn't let anything pass trough to the view lying behind it.
I have been working on this problem today and the suggestion to override -(void)contentWasTappedInPreviewContentController:(id)item {} is close but when you do you mess with the preview controllers handling.
So I stopped overriding that method and instead created a RAC signal that fires whenever the method is called. This does not mess with the default behavior of QL. I am doing it in a subclass of the QLPreviewController but that shouldn't be necessary.
I have a property on my class:
#property RACSignal *contentTapped;
Then in my init method of my subclass of QLPreviewController:
_contentTapped = [self rac_signalForSelector:#selector(contentWasTappedInPreviewContentController:)];
Now in another class or even internally you can use the signal like this:
previewController.contentTapped subscribeNext:^(id x) {
// Put your handler here!
}];
Here is my solution (to use KVO), where I'm monitoring navigation bar status - and showing toolbar when needed (it seems that it hides toolbar by itself when tapped)
#define kNavigationBarKeyPath #"navigationBar.hidden"
static void * const NavigationBarKVOContext = (void*)&NavigationBarKVOContext;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setToolbarHidden:NO];
[self.navigationController addObserver:self forKeyPath:kNavigationBarKeyPath options:NSKeyValueObservingOptionPrior context:NavigationBarKVOContext];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.navigationController removeObserver:self forKeyPath:kNavigationBarKeyPath];
}
And
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ( context == NavigationBarKVOContext ) {
BOOL prior = [change[NSKeyValueChangeNotificationIsPriorKey] boolValue];
if ( prior && self.navigationController.toolbarHidden ) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController setToolbarHidden:NO animated:YES];
});
}
}
}
I found none of the answers here to work, but the one that did for me was to subclass QLPreviewController and override viewDidAppear as so:
- (void)viewDidAppear:(BOOL)animated
{
UITapGestureRecognizer *gestTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showControls:)];
gestTap.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:[gestTap autorelease]];
}
Ok, solution is very simple.
just added an overlay view onto keyWindow. Attached gesture recognizers onto overlay and it works.
QLPreviewController* qlpc = [QLPreviewController new];
qlpc.delegate = self;
qlpc.dataSource = self;
qlpc.currentPreviewItemIndex=qlIndex;
[navigator pushViewController:qlpc animated:YES];
qlpc.title = [path lastPathComponent];
UIView* overlay = [[[UIView alloc] initWithFrame:navigator.view.bounds] autorelease];
[[[UIApplication sharedApplication] keyWindow] addSubview:overlay];
[overlay setNeedsDisplay];
[qlpc setToolbarItems:[NSArray arrayWithObjects:self.dirBrowserButton,self.space, self.editButton, self.btnSend, nil] animated:YES];
UITapGestureRecognizer* gestTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showControls:)];
gestTap.cancelsTouchesInView=NO;
[overlay addGestureRecognizer:[gestTap autorelease]];
[qlpc release];
Subclass QLPreviewController and then override
-(void)contentWasTappedInPreviewContentController:(id)item
{}
Thats its !
I have a simple (view-based) application. I want on tapping on custom UIView my button moved somewhere inside that view (for example to point 10,10).
My custom UIView is DrawView (DrawView.h and DrawView.m).
RotatorViewController (h. and .m).
I add to my DrawView an UIButton, connect with outlets my DrawView and UIButton. I add UITapGestureRecognizer in RotatorViewController and #selector(tap:).
Here is code of UITapGestureRecognizer
- (void)viewDidLoad
{
[super viewDidLoad];
UIGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:drawView action:#selector(tap:)];
[drawView addGestureRecognizer:tapGR];
[tapGR release];
}
#selector(tap:)
- (void) tap:(UITapGestureRecognizer *)gesture {
myButton.transform = CGAffineTransformMakeTranslation(10, 10);
}
But when i tap anywhere in DrawView application crashes. Here is log from console
2011-02-23 20:59:24.897 Rotator[7345:207] -[DrawView tap:]: unrecognized selector sent to instance 0x4d0fa80
2011-02-23 20:59:24.900 Rotator[7345:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DrawView tap:]: unrecognized selector sent to instance 0x4d0fa80'
I need your help
You said :
UITapGestureRecognizer in
RotatorViewController and
#selector(tap:)
and you wrote :
UIGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:drawView action:#selector(tap:)];
It means the actions is performed in the drawView delegate but you defined the selector tap: in the RotatorViewController.
I think you just have to replace the target drawView by self
UIGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)];
The error indicates that instances of the DrawView class do not respond to tap: messages. I see that you have defined a tap: method in DrawView.m, but have you declared that method in the header? Like so:
DrawView.h
#class UITapGestureRecognizer;
#interface DrawView {
UIButton *myButton;
}
- (void) tap:(UITapGestureRecognizer *)gesture;
#end