How to use touchesBegan from one UIView in another UIViewController - objective-c

I have made a graph with data in a UIView called HeartrateGraph. In a UIViewController named HRGraphInfo, I have a connected label that should output values when the graph is touched. The problem is, I don't know how to send a touched event using delegates from the UIView to the UIViewController.
Here is my touch assignment code in the UIView:
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
for (int i = 0; i < kNumberOfPoints; i++)
{
if (CGRectContainsPoint(touchAreas[i], point))
{
graphInfoRF.heartRateGraphString = [NSString stringWithFormat:#"Heart Rate reading #%d at %# bpm",i+1, dataArray[i]];
graphInfoRF.touched = YES;
break;
}
}
This segment of code is in a touchesBegan and properly stores the data value and number in the object graphInfoRF (I just did not show the declarations of dataArray, kNumberOfPoints, etc).
I am able to access graphInfoRF in the UIViewController using:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (graphInfoRF.touched == YES) {
self.heartRateLabel.text = graphInfoRF.heartRateGraphString;
}
else {
self.heartRateLabel.text = #"No data got over to this file";}
}
The label will show the correct string, but only after the data point on the graph is touched AND the label is touched right after. How do I change the touchesBegan so that once I touch the data point on the graph it will fill the label with the data automatically without the need for a second and separate touch on the label?

All ViewControllers comes with a single view it manages once it's initialized. You should be familiar with this view, you see it whenever you use a ViewController in the Interface Builder and you can access it using self.view if you're modifying a subclass.
Since ViewControllers come with a view, it also receives touch events for that view. Implementing touchesBegan in the ViewController will then receive events for that view, and normally any subviews that view is managing. Since you've down your own implementation of 'touchesBegan' in your HeartRateGraph, and since HeartRateGraph is a subview of ViewControllers main view, HeartRateGraph will receive and handle the touch event first before the ViewController ever has a chance to receive and handle the event like it normally would (think of bubbling up).
So what's happening is, the code to change the label in ViewController is only called when the label is touched because the label is a subview of the ViewController's main view... and also label doesn't have its own touches implementation, so ViewController and is able to retrieve and handle the event the way you want only when you click somewhere outside the graph. If you understand then there are two ways to solve this.
Either pass the event up to your superview
[self.superview touchesBegan:touches withEvent:eventargs];
or the proper recommended way of doing it:
Protocols and Delegates where your View makes a delegate call to it ViewController letting it know the graph has been touched and the ViewController needs to update its contents

Related

UIControl subclass - how to stop touch event flow?

I am writing a board game app (like chess). The main view recognizes swipe gestures (UISwipeGestureRecognizer) started anywhere in its fullscreen view, which make the board rotating.
Now I added a square-shaped transparent subview exactly over the board. This is an UIControl subclass that detects touches - as moves of pawns around the board:
[self.view addSubview:self.boardControl]
I expected that swipe gestures will be blocked at the area of screen that is covered with UIControl subclass. And they are not. So when I make a quick touch and drag (a swipe) over my square boardControl, it first is detected as a pawn move, but then is detected again as swipe which rotates the board.
How can I make my UIControl subclass to block a flow of touch events started in its frame to its superviews?
I can prevent my app from reacting to swipe gestures, by filtering touches at the level of superview, either by:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint location = [gestureRecognizer locationInView:self.view];
CGRect frame = self.boardControl.frame;
if ( CGRectContainsPoint(frame, location) )
return NO;
return YES;
}
or by:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
CGPoint location = [touch locationInView:self.view];
CGRect frame = self.boardControl.frame;
if (CGRectContainsPoint(frame, location))
return NO;
return YES;
}
but I want to solve that issue one level earlier: I want boardControl to not pass up any of started within their frame touch events, higher in views hierarchy.
Can UIControl subclass "cover" its superview, and "eat" all touches it gets, so the superview will not need to access its frame to guess if such a touch has to be filtered out or not?
All you need is implement UIGestureRecognizerDelegate is provides way to make requested things.
I think you should start from gestureRecognizer:shouldReceiveTouch: examples

ViewController calling touchesMoved only once

I have a question that I've searched but can't find a definative answer to. Here is my layout:
UIView - ViewController
|_UIScrollView - added programatically
| |_UIView to hold a backgound/perimeter - added programmatically
|_UIView 1 - added programmatically
|_UIView 2 - added programmatically
and so on
My question is how come the ViewController calls "touchesMoved" only once when I move say UIView 2 on touch?
Now UIView has it's own touchesMoved method, but I need the controller's touchesMoved to get called as I need it to talk to the ScrollView to update its position. Such as when that UIView 2 is near the corner, so that the ScrollView moves a little to fully show UIView 2.
If there is no way around this is there a way to update ScrollView from UIView 2 to scroll when its near a corner?
Edit:
I think I may have found a work around. Not sure if this will be accepted by Apple but:
I just made a call to a instance variable that is = to self.superview which then allows me to talk back to my ScrollView within UIView's touchesMoved
in that i can call the method [ScrollView setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated] so my ScrollView gets updated as the subview(UIView2) moves close to the edge of the UIWindow.
Thank you for the suggestions.
The behavior you describe is the result of the UIScrollView hijacking the touch moved event. In other words, as soon as the UIScrollView detect that a touch moved event falls within its frame, it takes control of it. I experienced the same behavior while trying so create a special swipe handler, and it failed each time a UIScrollView was also interested by the swipe.
In my case, I solved the issue by intercepting the event in sendEvent: overridden in my custom UIWindow, but I don't know if you want to do the same. In any case, this is what worked for me:
- (void)sendEvent:(UIEvent*)event {
NSSet* allTouches = [event allTouches];
UITouch* touch = [allTouches anyObject];
UIView* touchView = [touch view];
//-- UIScrollViews will make touchView be nil after a few UITouchPhaseMoved events;
//-- by storing the initialView getting the touch, we can overcome this problem
if (!touchView && _initialView && touch.phase != UITouchPhaseBegan)
touchView = _initialView;
//-- do your own management of the event
//-- let the event propagate if you want also the default event management
[super sendEvent:event];
}
An alternative approach that you might investigate is attaching a gesture recognizer to your views -- they have a pretty high priority, so maybe the UIScrollView will not mess with them and it might work better for you.
If there is no way around this is there a way to update ScrollView from UIView 2 to scroll when its near a corner?
Have you tried to make the UIScrollView scroll by calling:
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated

Need to know which custom UIViews are within a tap

I am creating custom UIImageViews and placing on a UIImageView in a UIScrollView. When the user taps on the custom UIImageView, it presents a popover.
The issue i may have is if two of the custom UIImageViews are overlapping. I need to ask the user which one he wants.
how can i tell which custom UIImageViews are within a tap? I need each view to return itself if it detects a tap. If more then one view returns, then i can ask the user which one he wants.
each custom UIImageView has a UITapGestureRecognizer created:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(select)];
singleTap.numberOfTapsRequired = 1;
singleTap.delegate = self;
[self addGestureRecognizer:singleTap];
right now, only the top most custom UIImageView is getting the tap and displaying the popover.
I'm not sure how you planned on identifying which image was which, but for this example I've used tags. The following will receive the location of a touch within the scroll view, and compare that point to frames of the image views in the scrollviews subviews. It will then add the tags of the images that matched to a mutable array.
NOTE: If you don't empty this array when you dismiss the alert new objects will be continuously added to it.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:myScrollView];
for (UIImageView *myImageView in myScrollView.subviews) {
if (CGRectContainsPoint(myImageView.frame, location)) {
[someMutableArray addObject:[NSNumber numberWithInteger:myImageView.tag]];
}
}
}
I assume by your question that the views are transparent so the user can see that in fact there is overlap, and may intentionally tap the area of overlap.
In any case what you need to do in this case is get the location of the tap:
[tapGesture locationInView:scrollView]
Then walk the scrollView's subView array, getting each of your UIImageView's, getting its frame, and seeing if the tap is inside that frame.
Now you have an array of possible images - you can pop an action sheet (whatever) and ask the user which to show.

Adding touch-area to UIView (iOS5)

I have a MainView with MainViewController. Now I want to add a specific area where I want to register touches (painting in a specific area). How could I do this?
I thought about adding a sub-view with its own sub-viewcontroller, but this guy tells this is not a good approach.
The post you linked to is partially out of date because it was written before Apple introduced support for View Controller Containment in iOS 5.
That said, it's your choice whether:
the subview is managed by its own view controller or
you use the MainViewController directly to respond to touches in the subview or
you create a UIView subclass that interprets touches on itself without the help of a view controller.
Add a custom view as a property, called touchArea
-(void) touchesBegan/Moved/Ended (NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [[touches anyObject] locationInView:self.view];
if (CGRectContainsPoint(touchArea.frame, location))
//code
}
}

ContinueTrackingWithTouch:withEvent: method is not being called?

I have a custom UIView class. Inside that view, there is an image view. (I'm making a UISlider from scratch).
I am trying to get the image view -- the thumb of the slider -- to move across the view. Below is the code from the UIView class
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchPoint = [touch locationInView:self];
if ( CGRectContainsPoint(self.thumb.frame, touchPoint))
self.thumb.center = CGPointMake(touchPoint.x, self.thumb.center.y);
return YES;
}
When I place my finger on the thumb of the slider and try to drag it, nothing happens. However, when I touch outside the thumb of the slider, drag my finger to the thumb without letting go, and then try dragging the thumb, it works fine.
How can I modify my code so that the method will be called when someone holds the thumb and tries to drag it?
My guess is that you have user interaction enabled on your image view, which is in turn not relaying the touch events to the backing logic view. Do something like this:
self.thumb.userInteractionEnabled = NO
Put that in whatever init method you are using and the thumb view will not interrupt the touch events anymore.