Objective-C – Making several views respond to the same touch event? - objective-c

As you all know in Cocoa-touch the GUI is made up of several UIViews. Now in my view I have an UIImageView (containing an image) which is aligned at x:10 y:10, then I have a UILabel aligned at x:30 y:10 and then finally another UILabel aligned at x:50 y:10.
Now I would want all these UIViews to respond to the same touch event. What would be the easiest way to accomplish this? Would it be to create an UIView that will align from x:10 y:10 and cover all the views and then place this view on top of these views (-bringSubviewToFront)?
Cheers,
Peter

I think your variant is ok! Just create UIView and catch all touch events by it.
Or you can put all your subviews (image, label, label) on one subview mergeView of main view, disable user interaction of that views (image, label, label) and to mergeView add gesture recognizer or whatever you want to catch touches. With such approach it will be easier to reposition your views: just reposition mergeView. Don't forget to enable user interaction of mergeView.

Catch the touch events on the top View and explicitly pass those touches to other views you are interested in. But makes sure the touch point interesects the other views to whom you are sending the touch events
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch = [[touches allObjects] objectAtIndex:0];
CGPoint point = [touch locationInView:topView];
if (CGRectContainsPoint(view1.frame, point)) {
[view1 touchesBegan:[NSSet setWithObject:touch] withEvent:nil];
}
if (CGRectContainsPoint(view2.frame, point)) {
[view2 touchesBegan:[NSSet setWithObject:touch] withEvent:nil];
}
}

Related

Sliding UIView in a UICollectionViewCell is blocking the ability to scroll

I am using a UIPanGestureRecognizer on a UIView that is in a UICollectionViewCell. I am using the translationInView method to get the translation along the X axis and slide the UIView left and right. It works fine, but now I cannot scroll up and down in the CollectionView if my finger is on a cell. Is there a way to make the PanGestureRecognizer pass the vertical scrolling to the UICollectionView?
I am trying to replicate the sliding seen in Reeder.app for iOS.
Once the UIPanGestureRecognizer activates in your UIView touches are no longer forwarded through the responder chain, because of that your UICollectionView is not receiving the touches anymore.
You can try setting your UIView as the delegate of your UIPanGestureRecognizer and place the logic of whether the UIPanGestureRecognizer should activate. You have to implement the delegate's method in your UIView:
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
For example with the code below you would tell it not to activate if the velocity on the y axis of the view is greater than the velocity of the x axis (therefore sending the touches to the UICollectionView under it)
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
UIPanGestureRecognizer *recognizer = (UIPanGestureRecognizer *)gestureRecognizer;
CGPoint velocity =[recognizer velocityInView:self];
if(abs(velocity.y)>=(abs(velocity.x))){
return NO;
}else return YES;
}
Hope this is helpful.

Touch on UISlider prevents scrolling of UIScrollView

I have a (vertical) UISlider inside a UIScrollview. I'd like to be able to change the value of the slider, and, without lifting my finger, scroll the scrollview left or right.
Desired behavior:
Touch down inside vertical UISlider, followed by a finger drag left or right causes the scrollview to scroll
Actual behavior:
Touch down inside vertical UISlider, followed by a finger drag left or right causes no movement in UIScrollview. A touch down outside the UISlider followed by a drag will scroll the scrollview as expected
UIView has a property called exclusiveTouch which seems as if it might be related to my problem. I tried setting it to NO, with no luck.
So, how can is set up my UISliders so that the scrollview beneath them will respond to touches which originate inside the UISliders?
Have you tried subclassing UIScrollView and implementing - (BOOL)touchesShouldCancelInContentView:(UIView *)view? According to the Apple documentation:
// called before scrolling begins if touches have already been delivered to a subview of the scroll view. if it returns NO the touches will continue to be delivered to the subview and scrolling will not occur
// not called if canCancelContentTouches is NO. default returns YES if view isn't a UIControl
If you simply return NO if the view is your UISlider, this may do what you want, assuming your UIScrollView only scrolls horizontally. If this doesn't work, you likely will have to do custom touch handling (ie. overriding touchesBegan:withEvent:, touchesChanged:withEvent:, etc.) for both your UIScrollView and your UISlider.
What you are seeing is the intended behavior.
Each touch event only gets handled by one control. What exclusiveTouch does is actually to prevent other touch events from being delivered to other views.
To do what are trying to do you would have to do some of the touch handling yourself. Passing the event to both your views. You could do either do it by implementing all the touchesBegan:, touchesMoved: etc. methods and pass the events to both views. You can read more about that approach in the UIResponder documentation. Another approach is to do the event handling in a UIGestureRecognizer on the scroll view that hit tests the slider and updates the value of the slider using the y-delta. You can read more about gesture recognizers and event handling in the section about Gesture Recognizers in the Event Handling Guide for iOS.
Side note:
Go to the Settings app and toggle a switch half way (for example the Airplane mode toggle) and then drag down. Nothing will happen. The rest of the OS behaves the same way. Are you sure that this is the interaction that you really want to do? Apps that behave differently often feel weird and unfamiliar.
Your question confused me a bit. You are saying a vertical slider - but dragging left and right?
If you wish to scroll the scrollview when dragging the UISlider, the proper way to do so is
[mySlider addTarget:self action:#selector(sliderMoved:) forControlEvents:UIControlEventValueChanged];
and
- (void) sliderMoved:(UISlider*) slider {
myScrollView.contentOffset.x = slider.value * (myScrollView.contentSize.width - myScrollView.bounds.size.width);
}
Hope this is what you want.
You need to set delaysContentTouches as NO and prevent for UISlider objects to scroll, Check below code.
mySlider.delaysContentTouches = NO;
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view {
if ([view isKindOfClass:[UISlider class]])
{
UITouch *touchEvent = [[event allTouches] anyObject];
CGPoint locationEvent = [touchEvent locationInView:view];
CGRect thumbRect;
UISlider *mySlide = (UISlider*) view;
CGRect trackRect = [mySlide trackRectForBounds:mySlide.bounds];
thumbRect = [mySlide thumbRectForBounds:mySlide.bounds trackRect:trackRect value:mySlide.value];
if (CGRectContainsPoint(thumbRect, locationEvent))
return YES;
}
return NO;
}
I think you can get some reference from this example in this example it is shown that how to cancel any touch or any gesture recognizers and apply them to other views.
May this lead you to the solution of your problem and if it will just let me know about it
Happy Codding :)

Trouble with tap detection on CCLayer subclass

I have a CCLayer subclass MyLayer in which I handle touch events:
(BOOL) ccTouchBegan:(UITouch *) touch withEvent:(UIEvent *) event
I set the content size of MyLayer instances like this:
`myLayer.contentSize = CGSizeMake(30.0, 30.0);`
I then add MyLayer instances as children of ParentLayer. For some reason I can tap anywhere on the screen and a MyLayer instance will detect the tap. I want to only detect taps on the visible portion/content size. How can I do this?
Are the MyLayer instances somehow inheriting a "tappable area" from somewhere else? I've verified that the contentSize of the instance just tapped is (30, 30) as expected. Perhaps the contentSize isn't the way to specify the tappable area of CCLayer subclass.
When the touch is enabled on a particular CCLayer, it receives all of the touch events in the window. That being said, if there are multiple layers, all layers will receive the same touches.
To alleviate this, obtain the location from the UITouch, convert it to Cocos2d coordinates, then check to see if it is within the bounds of the Layer you are concerned with.
Here's some code to work with:
CCLayer * ccl = [[CCLayer alloc] init];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
if (CGRectContainsPoint(CGRectMake(ccl.position.x - ccl.contentSize.width/2, ccl.position.y - ccl.contentSize.height/2, ccl.contentSize.width, ccl.contentSize.height), location)) {
//continue from there...
}

Drawing Straight Lines with Finger on iPhone

Background: I am trying to create a really simple iPhone app that will allow the user to draw multiple straight lines on the screen with their finger.
I'm using these two methods in my UIViewController to capture the coordinates of each line's endpoints.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
Question:
I would like the line to show up as soon as touchesEnded has fired and then keep drawing more lines on the screen. How do I do this? I don't necessarily need the code, but I need help with the big picture idea of how to put it together. Also, I'm not a huge fan of xibs and like to do things all programmatically if that affects the answer.
What I've Tried: I've tried using Quartz 2d but it seems in order to use that, you have to do your drawing in the drawRect method of a separate subclassed view. So I would have to create a new view for each line? and then my coordinates would be messed up b/c I'd have to translate the touches positions from the UIViewController to the view.
I've also tried with OpenGL, which I've had a bit more success with (using the GLPaint sample as a template) but OpenGL seems like overkill for just drawing some straight lines on the screen.
You don't need multiple views, and you don't need OpenGL.
Make a subclass of UIView -- call it CanvasView.
Make an object to represent "a line" in your canvas -- it would be a subclass of NSObject with CGPoint properties for start and end.
CanvasView should keep an array of the lines that are in the canvas.
In -[CanvasView drawRect:], loop through the lines in the array, and draw each one.
In -[CanvasView touchesBegan:withEvent:], stash the start point in an instance variable.
In -[CanvasView touchesEnded:withEvent:], make a new line with the start and end points, and add it to your array of lines. Call [self setNeedsDisplay] to cause the view to be redrawn.
U mentioned that you just need big picture of the idea..
So here it is..
Take a view, subclass of UIView.
Capture touches events and then draw it in drawRect: method..
This is all what #kurtRevis mentioned in his answer.
Now to avoid slowing down of drawing, keep an offline image that keeps updated screen view and then just go on adding lines over that. So you wont get slowing down performance when there is big array of lines to be drawn.
Hope u are getting me.
As far as i know you can also use Core Graphics to draw a line, and from your question you don't need to create views for every single line, instead your single view graphics context will be a drawing sheet for all the drawing, and you almost nearer to the solution. Just by taking the touch coordinate you can draw the lines on the view.
CGPoint previousPoint;//This must have the global scope, it will be used in all the touch events
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
previousPoint = [touch locationInView:self]; // take the starting touch point;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint currentPoint;
UITouch *touch = [touches anyObject];
currentPoint = [touch locationInView:self];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextMoveToPoint(context, previousPoint.x,previousPoint.y);
CGContextAddLineToPoint(context, currentPoint.x,currentPoint.y);
CGContextStrokePath(bluecontext);
}
Hope this helps you, Let me know any issue....

Drag and drop UIButton but limit to boundaries

I’m not asking for givemesamplecodenow responses, just a nudge in the right direction would be much appreciated. My searches haven’t been any good.
I have a UIButton that I am able to move freely around the screen.
I would like to limit the drag area on this object so it can only be moved up and down or side to side. I believe I need to get the x,y coordinates of the boundaries and then restrict movement outside this area. But that’s a far as I have got. My knowledge doesn’t stretch any further than that.
Has anyone implemented something similar in the past?
Adam
So let's say you're in the middle of the drag operation. You're moving the button instance around by setting its center to the center of whatever gesture is causing the movement.
You can impose restrictions by testing the gesture's center and resetting the center values if you don't like them. The below assumes a button wired to an action for all Touch Drag events but the principle still applies if you're using gesture recognizers or touchesBegan: and friends.
- (IBAction)handleDrag:(UIButton *)sender forEvent:(UIEvent *)event
{
CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
if (point.y > 200)
{
point.y = 200; //No dragging this button lower than 200px from the origin!
}
sender.center = point;
}
If you want a button that slides only on one axis, that's easy enough:
- (IBAction)handleDrag:(UIButton *)sender forEvent:(UIEvent *)event
{
CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
point.y = sender.center.y; //Always stick to the same y value
sender.center = point;
}
Or perhaps you want the button draggable only inside the region of a specific view. This might be easier to define if your boundaries are complicated.
- (IBAction)handleDrag:(UIButton *)sender forEvent:(UIEvent *)event
{
CGPoint point = [[[event allTouches] anyObject] locationInView:self.someView];
if ([self.someView pointInside:point withEvent:nil])
{
sender.center = point;
//Only if the gesture center is inside the specified view will the button be moved
}
}
Presumably you'd be using touchesBegan:, touchesMoved:, etc., so it should be as simple as testing whether the touch point is outside your view's bounds in touchesMoved:. If it is outside, ignore it, but if it's inside, adjust the position of the button.
I suspect you may find this function useful:
bool CGRectContainsPoint ( CGRect rect, CGPoint point );