I'm using UIGestureRecognizer in my iOS application and I'm having some issues.
I only want the gestures to work in a certain area of the view, so I made a new UIView with a specific frame and added it to the root view. The gestures are working fine with this, but the only issue now is that I can't click the stuff that is under/behind that new view (the objects that are on the root view). If I set userInteractionEnabled to NO, it breaks the gestures so that is not an option.
What can I do to fix that?
Thanks.
Don't create a new view for your gesture recognizer. The recognizer implements a locationInView: method. Set it up for the view that contains the sensitive region. On the handleGesture, hit-test the region you care about like this:
0) Do all this on the view that contains the region you care about. Don't add a special view just for the gesture recognizer.
1) Setup mySensitiveRect
#property (assign, nonatomic) CGRect mySensitiveRect;
#synthesize mySensitiveRect=_mySensitiveRect;
self.mySensitiveRect = CGRectMake(0.0, 240.0, 320.0, 240.0);
2) Create your gestureRecognizer:
gr = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self.view addGestureRecognizer:gr];
// if not using ARC, you should [gr release];
// mySensitiveRect coords are in the coordinate system of self.view
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer {
CGPoint p = [gestureRecognizer locationInView:self.view];
if (CGRectContainsPoint(mySensitiveRect, p)) {
NSLog(#"got a tap in the region i care about");
} else {
NSLog(#"got a tap, but not where i need it");
}
}
The sensitive rect should be initialized in myView's coordinate system, the same view to which you attach the recognizer.
Yo can also do:
gestureRecognizer.delegate = self
somewhere. generally on viewDidLoad(). then you implement the method:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let view = self.getTheViewDontWannaConsider() /* or whateva */
let point = touch.location(in:view)
if point.y >= 50 /* or whateva calc. you want */ {
return false
}
return true
}
Related
I am displaying a image from code here is the code
UIImageView *preArrowImage =[[UIImageView alloc]init ];
preArrowImage.image =[UIImage imageNamed:#"arrowprev.png"];
preArrowImage.frame = CGRectMake(20, 60, 10, 30);
[self.view addSubview:preArrowImage];
I want to handle the touch event on the preArrowImage programmatically.
SWIFT 5
let preArrowImage : UIImageView // also give it frame
let singleTap = UITapGestureRecognizer(target: self, action: #selector(tapDetected))
preArrowImage.isUserInteractionEnabled = true
preArrowImage.addGestureRecognizer(singleTap)
//Action
func tapDetected() {
print("Imageview Clicked")
}
Objective-c
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapDetected)];
singleTap.numberOfTapsRequired = 1;
[preArrowImage setUserInteractionEnabled:YES];
[preArrowImage addGestureRecognizer:singleTap];
-(void)tapDetected{
NSLog(#"single Tap on imageview");
}
Simply add a UITapGesture on the image but remember to make its UserInteraction Enabled.
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(singleTapGestureCaptured:)];
[preArrowImage addGestureRecognizer:singleTap];
[preArrowImage setMultipleTouchEnabled:YES];
[preArrowImage setUserInteractionEnabled:YES];
Now in Swift!
let singleTap = UITapGestureRecognizer(target: self, action: Selector("tapDetected"))
singleTap.numberOfTapsRequired = 1
preArrowImage.userInteractionEnabled = true
preArrowImage.addGestureRecognizer(singleTap)
//Action
func tapDetected() {
println("Single Tap on imageview")
}
Swift 3
On UIImageView enable UserInterAction
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
if let touch = touches.first {
if touch.view == self.imgVwPostPreview { //image View property
//Do your actions
//self.delegate?.didClickOnCellImageView(indexPath: self.cellIndexPath!)
}
}
}
Using story board with view in view controller.
After selecting one of the gesture recognizer drag it to your controller.
Now it will display in "Document outline" and header of view controller xib.
Drag gesture to UILable
create IBAction method in header file
Now drag it from handler to gesture icon in xib
Xamarin.iOS
UIImageView preArrowImage; // also give it frame
var singleTap = new UITapGestureRecognizer(TapDetected);
singleTap.ShouldReceiveTouch -= TapGesture_ShouldReceiveTouch;
singleTap.ShouldReceiveTouch += TapGesture_ShouldReceiveTouch;
preArrowImage.UserInteractionEnabled = true;
preArrowImage.AddGestureRecognizer(singleTap)
bool TapGesture_ShouldReceiveTouch(UIGestureRecognizer recognizer, UITouch touch)
{
return true;
}
void TapDetected(UITapGestureRecognizer tapGestureRecognizer)
{
print("Imageview Clicked")
}
If your UIImageView is an IBOutlet which you get from a Storyboard or Nib, you could add a UIGestureRecognizer to the image view, enable user interaction and connect a IBAction to the gesture recognizer.
For more info check my answer on this question: How to you make a UIImageView on the storyboard clickable (swift)
Here's another option: Cheat.
I mean, think laterally.
Set up your UIImage how you want, cropped, aspect fit, whatever.
Overlay this with a UIView (equal dimensions, position etc.)
Set the background to clearcolour, and the class to UIControl.
Point the touch up inside event to your handler, and voila.
Currently I am working for a collage app. I want to draw collage frame and after that I need to add images from Camera roll or camera . I need the following type view
I have added 5 UIImageViews. To draw in shape I have added UIBezierPath like for imageview1
UIBezierPath *path1 = [[UIBezierPath alloc] init];
[path1 moveToPoint:CGPointMake(0, 0)];
[path1 addLineToPoint:CGPointMake(150, 0)];
[path1 addLineToPoint:CGPointMake(0, 150)];
[path1 addLineToPoint:CGPointMake(0, 0)];
UIImageView *imgView1 = [[UIImageView alloc] initWithFrame:CGRectMake(140, 0, 160, 300)];
imgView1 . tag = 2;
imgView1 . userInteractionEnabled = YES;
imgView1. backgroundColor = [UIColor greenColor];
CGPathRef borderPathRef2 = [path1 CGPath];
CAShapeLayer *borderShapeLayer2 = [[CAShapeLayer alloc] init];
[borderShapeLayer2 setPath:borderPathRef2];
[[imgView1 layer] setMask:borderShapeLayer2];
imgView1.layer.masksToBounds = YES;
[borderShapeLayer2 release];
[view1 addSubview:imgView1];
Like this I have done for all 5. But after adding imageView5 touch is not detected on all other 4 views because its frame overlaping on the other four imageview.
So I am not getting how to design this . I need to add images to all imageviews by touch action.
Please help me. If someone have any idea about this then please share.
Thanks In Advance.
Following is the example 2 which is from Insta Collage app.
I have solved this problem by overriding hitTest:withEvent: method of UIView.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
//Getting View which is touched.
UIView *testView = [super hitTest:point withEvent:event];
//If this is self. and point hitted on Masked Layer(Which is hiding our UIView).
if(testView == self && testView.layer.mask != nil && CGPathContainsPoint([(CAShapedLayer*)testView.layer.mask path], NULL, point, false))
{
//Then return nil. So Touch thinks that UIView has not clicked. and touch is passed to UIView which is behind this view. And again hitTest is performed on inner UIViews.
testView = nil;
}
//Return nil. So touch thinks that UIView is not clicked.
//Return self. So touch thinks self is clicked. and no more hitTest is performed.
return testView;
}
Now i implemented a control called IQIrregularView for this problem.
Check this.
https://github.com/hackiftekhar/IQIrregularView
You should try random shaped buttons
you can find your solution in this custom class found on GitHub
It worked for me and hope will work for you also....
Happy Coding...........
I was also trying to detect a touch on a UIView within the path of a UIBezierPath, using Swift
My scenario is:
- UIView (Container)
- UIImageView (masked with Triangle UIBezierPath)
- UIImageView (masked with Triangle UIBezierPath)
In my container, I override hitTest and return the subview that was touched like so:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for subview in subviews.reversed() {
// ** convert the touch to the subview space:
let convertedPoint = subview.convert(point, from: self)
// ** if the subview layer mask exists I know that it has a UIBezierPath:
if let layerShape = subview.layer.mask as? CAShapeLayer {
if let shapePath = layerShape.path, shapePath.contains(convertedPoint) {
// ** return the subview with the path that contains the converted point
return subview
}
}
}
return nil
}
Hope that helps!
I'm working in iOS5, and apparently I should be able to control or at least subdue the UIScrollView's internal pinch gesture recognizer using scrollView.pinchGestureRecognizer.
However, my code does not seem to work. The recognizer does not treat my class as a delegate and does not wait for my rotation gesture recognizer to fail. What can I do to make the rotation gesture a priority, after which the pinch would be considered?
More precisely, the issue that I'm running in is that the view that is being rotated and zoomed at the same time "flies off the screen" towards the bottom left corner, never to be seen again.
-(void)setup scrollViews
{
[tempScrollView.pinchGestureRecognizer requireGestureRecognizerToFail:rotationRecognizer];
tempScrollView.pinchGestureRecognizer.delegate = self;
tempScrollView.maximumZoomScale = 4.0;
tempScrollView.minimumZoomScale = 0.25;
//
tempScrollView.delegate = self;
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if([gestureRecognizer isEqual:rotationRecognizer])
{
NSLog(#"Rotation gesture");
}else {
NSLog(#"Other gesture: %#", [gestureRecognizer class]);
}
return YES;
}
- (IBAction)rotateView:(id)sender {
if([sender isKindOfClass:[UIRotationGestureRecognizer class]])
{
UIRotationGestureRecognizer* recognizer = sender;
float recognizerRotation = [recognizer rotation];
CGAffineTransform transform = CGAffineTransformMakeRotation(recognizerRotation);
activeView.transform = transform;
}
}
As far as I know, the pinchGestureRecognizer in UIScrollView is read only.
However, you might try to subclass UIScrollView and override the method addGestureRecognizer: to disable the pinchGestureRecognizer, then add your own custom pinchGestureRecognizer.
I have a uiview at the top of the interface (below the status bar) that only the bottom part of it is shown.
Actually, I want to make the red uiview to slide down to be entirely shown by drag such as the notificationcenter in the native iOS and not just by taping a button.
What should I use to "touch and pull down" the uiview so it could be shown entirely ?
No needs to find a workaround of drag-n-drop. An UIScrollView can do it without any performance loss brought by listening on touches.
#interface PulldownView : UIScrollView
#end
#implementation PulldownView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) {
return self;
}
self.pagingEnabled = YES;
self.bounces = NO;
self.showsVerticalScrollIndicator = NO;
[self setBackgroundColor:[UIColor clearColor]];
double pixelsOutside = 20;// How many pixels left outside.
self.contentSize = CGSizeMake(320, frame.size.height * 2 - pixelsOutside);
// redArea is the draggable area in red.
UIView *redArea = [[UIView alloc] initWithFrame:frame];
redArea.backgroundColor = [UIColor redColor];
[self addSubview:redArea];
return self;
}
// What this method does is to make sure that the user can only drag the view from inside the area in red.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (point.y > height)
{
// Leaving useless touches to the views under it.
return nil;
}
return [super hitTest:point withEvent:event];
}
#end
How to use:
1. Initialize an instance of PulldownView.
2. Add any content you want to display to the instance using [addSubview:].
3. Hide the area in red.
[pulldownView setContentOffset:CGPointMake(0, heightOfTheView - pixelsOutside)];
This is a simple example. You can add any features to it like adding a titled button bar on the bottom of the draggable area to implement click-n-drop, or adding some method to the interface to reposition it by the caller.
Make a subclass of UIView.
Override touchesBegan:withEvent and touchesMoved:withEvent.
In the touchesBegan perhaps make a visual change so the user knows they are touching the view.
In the touchesMoved use
[[touches anyObject] locationInView:self]
and
[[touches anyObject] previousLocationInView:self]
to calculate the difference between the current touch position and the last touch position (detect drag down or drag back up).
Then if you're custom drawing, call [self setNeedsDisplay] to tell your view to redraw in it's drawRect:(CGRect)rect method.
Note: this assumes multiple touch is not used by this view.
Refer to my answer in iPhone App: implementation of Drag and drop images in UIView
You just need to use TouchesBegin and TouchesEnded methods. In that example, I have shown how to use CGPoint, Instead of that you have to try to use setFrame or drawRect for your view.
As soon as TouchesMoved method is called you have to use setFrame or drawRect (not sure but which ever works, mostly setFrame) also take the height from CGPoint.
I am struggling to get the behaviour I would like from the gesture recognisers, specifically cancelling certain gestures if others have fired.
I have a scrollView set to paging and multiple subviews in each page. I have added a touch gesture recogniser to scroll to the next or prev page if the user taps to the right or left of the page.
// Add a gesture recogniser turn pages on a single tap at the edge of a page
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGestureHandler:)];
tapGesture.cancelsTouchesInView = NO;
[self addGestureRecognizer:tapGesture];
[tapGesture release];
and my gesture handler:
- (void) tapGestureHandler:(UIGestureRecognizer *) gestureRecognizer {
const CGFloat kTapMargin = 180;
// Get the position of the point tapped in the window co-ordinate system
CGPoint tapPoint = [gestureRecognizer locationInView:nil];
// If the tap point is to the left of the page then go back a page
if (tapPoint.x > (self.frame.size.width - kTapMargin)) [self scrollRectToVisible:pageViewRightFrame animated:YES];
// If the tap point is to the right of the page then go forward a page
else if (tapPoint.x < kTapMargin) [self scrollRectToVisible:pageViewLeftFrame animated:YES];
}
All works well, except where I have a subview on the page that has buttons in it. I want to be able to ignore the tap to turn the page if the user touches a button on the subView and I can't figure out how to do this.
Cheers
Dave
The solution that worked the best for me in the end was to use the hitTest to determine if there were any buttons underneath the location of the tap gesture. If there are then just ignore the rest of the gesture code.
Seems to work well. Would like to know if there are any gotchas with what I have done.
- (void) tapGestureHandler:(UIGestureRecognizer *) gestureRecognizer {
const CGFloat kTapMargin = 180;
// Get the position of the point tapped in the window co-ordinate system
CGPoint tapPoint = [gestureRecognizer locationInView:nil];
// If there are no buttons beneath this tap then move to the next page if near the page edge
UIView *viewAtBottomOfHeirachy = [self.window hitTest:tapPoint withEvent:nil];
if (![viewAtBottomOfHeirachy isKindOfClass:[UIButton class]]) {
// If the tap point is to the left of the page then go back a page
if (tapPoint.x > (self.bounds.size.width - kTapMargin)) [self scrollRectToVisible:pageViewRightFrame animated:YES];
// If the tap point is to the right of the page then go forward a page
else if (tapPoint.x < kTapMargin) [self scrollRectToVisible:pageViewLeftFrame animated:YES];
}
}
Apple documentation shows the answer:
- (void)viewDidLoad {
[super viewDidLoad];
// Add the delegate to the tap gesture recognizer
self.tapGestureRecognizer.delegate = self;
}
// Implement the UIGestureRecognizerDelegate method
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch: (UITouch *)touch {
// Determine if the touch is inside the custom subview
if ([touch view] == self.customSubview){
// If it is, prevent all of the delegate's gesture recognizers
// from receiving the touch
return NO;
}
return YES;
}
Of course in this case customSubview would be subview on the page that has buttons in it (or even the buttons on it)
Swift 3
Set UITapGestureRecognizer
let tap = UITapGestureRecognizer(target: self, action: #selector(Class.didTap))
tap.delegate = self
UITapGestureRecognizer delegate method:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view != buttonThatShouldCancelTapGesture
}
You can subclass UIView and overwrite the -touchesBegan selector, or you can play with the opaque property of the subviews to make them "invisible" to the touches (if view.opaque = NO, the view ignores touch events).