How to support touch move across various objects? - ios

I have some labels in my app like this...
What i need to do is, when clicking on a label, just I'm showing label name in the bottom of the screen. It works fine while clicking on each cell separately. But i want to show the changes even the user click on a particular label and move his finger on another label. That is, once he pressed on screen, where ever his finger moves, i want to trace those places and want to show the changes. How can i do this? Please explain briefly.
Thanks in Advance

By default, touch events are only sent to the view they started in. So the easiest way to do what you're trying to do would be to put all of your labels in a container view that intercepts the touch events, and let the container view decide how to handle the events.
First create a UIView subclass for the container and intercept the touch events by overriding hitTest:withEvent::
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// intercept touches
if ([self pointInside:point withEvent:event]) {
return self;
}
return nil;
}
Set that custom class as the class of the container view. Then, implement the various touches*:withEvent: methods on your container view. In your case, something like this should work:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// determine which view is under the touch
UIView* view = [super hitTest:[[touches anyObject] locationInView:self] withEvent:nil];
// get that label's text and set it on the indicator label
if (view != nil && view != self) {
if ([view respondsToSelector:#selector(text)]) {
// update the text of the indicator label
[[self indicatorLabel] setText:[view text]];
}
}
}

Related

UITapGestureRecognizer not recognized because view is nested in multiple views

I have a custom view that I present by tapping on either a button on the view controller or by tapping on a button on tableview cell (table view is a child of the view controller)
To dismiss the custom view I want the user to be able to tap anywhere on the screen to dismiss it. However due to the many hierarchies of the view in view controllers. A simple UITapGuestureRecognizer isn't working. Is there any workaround for a case like this?
Create a subclass of UIView, call it MyTapView. Assign this class your parent view, which holds all your subviews.
Override in your class the following to intercept any touches made to your view instance.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (!self.clipsToBounds && !self.hidden && self.alpha > 0) {
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
CGPoint subPoint = [subview convertPoint:point fromView:self];
UIView *result = [subview hitTest:subPoint withEvent:event];
if (result != nil) {
return result;
}
}
}
// use this to pass the 'touch' onward in case no subviews trigger the touch
return [super hitTest:point withEvent:event];
}
This method ignores view objects that are hidden, that have disabled user interactions, or have an alpha level less than 0.01. This method does not take the view’s content into account when determining a hit (feel free to modify this). Thus, a view can still be returned even if the specified point is in a transparent portion of that view’s content and now, after it has been overridden, receives touches outside the bounds.

Enable scrolling in a region for nested scroll views

I am working with nested scroll views as described here to create cross-directional scrolling.
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/UIScrollView_pg/NestedScrollViews/NestedScrollViews.html
Is it possible to define a region that the user can touch to scroll while still passing those touches to the nested scroll views?
Here is an example using the image from that documentation link. I only want the red region for UIScrollView A to be scrollable while still passing touches to UIScrollView B if the user scrolled on the right side.
The problem here is if I block touches using a method like -pointInside:withEvent: then the nested scroll views won't get the touch. I want the nested scroll views to accept scrolling in the entire view but the parent to only accept scrolling touches in part of it's view marked in red for this example.
You can overload method hitTest, in your parent view. This method should returns active scroll view.
And also you should write some custom code to manage touchable areas.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if (result)
{
if ([self scrollChildWithPoint:point])
{
result = self.childScrollView;
}
else if([self scrollParentWithPoint:point])
{
result = self.parentScrollView;
}
else
{
result = nil;
}
}
return result;
}

iOS/ObjC: "Fallback" tap gesture recognizer?

I have a UIViewController subclass whose view will generally contain some number of UIButtons and other interactive elements which may have one or more gesture recognizes attached to them.
What I'm trying to do is provide some visual feedback in the event that the user taps on a part of the screen that is not interactive. In other words: when the user taps the screen anywhere, if and only if no other control in the view responds to the touch event (including if it's, say, the start of a drag), then I want to fire off a method based on the location of the tap.
Is there a straightforward way to do this that would not require my attaching any additional logic to the interactive elements in the view, or that would at least allow me to attach such logic automatically by traversing the view hierarchy?
You can override the pointInside:withEvent: method of the container view and return NO if there is an interactive element under the tapped location, and return YES otherwise. The case where you return YES will correspond to tapping on an empty location.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// Perform some checks using the CGRectContainsPoint method
// e.g. CGRectContainsPoint(oneOftheSubviews.frame, point)
}
A good alternative is using the hitTest:withEvent: method as in the example below:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView != someView) return nil;
return hitView;
}
[super hitTest:point withEvent:event] will return the deepest view in that view's hierarchy that was tapped.
So you can check the type of hitView, and if it corresponds to one of your interactive subviews, return it. If it is equal to self, then it means that there isn't a subview under the tapped location, which in your case means that the user tapped on an empty area. In that case you'll do whatever you want to do and then return nil.

Restrict one view panning at a time

I have five views on which i have applied UIPanGestureRecognizer placed with some distance to each other having same Y-asix from top (box type shape).
I am dragging view with panning on x-axis. The problem is that i can drag multiple views with multiple touches. i want to restrict one view dragging at a time.
I tried using UIPanGestureRecognizer property
pan.maximumNumberOfTouches=1;
but that is for just single view. Any Ideas?
Try this too,
Lets say you have all this five views added on the view of a viewController. Then do the following;
myViewController.view.multipleTouchEnabled = NO;
This make the mainView to process only first touch and only this first touch will flow down the hierarchy (i.e. to subViews and their gestureRecognizers )
Hope this works.
Alernate:
Define this in the interface of your mainView or mainViewController that holds the 5 views.
#property(nonatomic,retain)UIPanGestureRecognizer *currentRecognizer;
Then apply the following code.
-(void)on_pan_gesture:(UIPanGestureRecognizer*) panGestureRecognizer
{
if(self.currentRecognizer != nil && [self.currentRecognizer isEqual:panGestureRecognizer])
{
//do the task of the selected gesture recognizer
//this recognizer will be active till the touches are not ended or cancelled.
//hence on the first recognizer will work.
}
else
{
//if there is not currentRecognizer then set this recognizer to be current.
self.currentRecognizer = panGestureRecognizer;
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
self.currentRecognizer = nil;
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
self.currentRecognizer = nil;
}
Use the gesture recognizers' delegate method
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
to make it return NO.

Detecting clicks outside of UIScrollView

I've implemented a paged scroll according to this technique
( iOS develop. How to extend UIScrollView's scroll event responding area? ) and it works just as intended.
The view that I'm scrolling is containing a couple of buttons and I want to be able to click not only those that are centered/paged into the scrollview but also those to the left and to the right of it. I cannot find any way to solve this but I'm not really an iOS-Jedi yet, hoping one of you are though :)
So as you can see from the screenie the UIScrollView is about a third of the width of the window, the contentsize of the UIScrollView is much larger: about 1500px and contains a lot of buttons added programmatically. The cool thing with this solution, and the part that actually works, is that the buttons:
1) are paged into the scrollview
2) are visible outside the scrollview (since "clip subviews" is unchecked for the scrollview) 3) the buttons are clickable when visible inside the uiscrollview.
BUT what doesn't work is simply this:
- the buttons currently being outside of the window does not receive "their" clicks when clicking on them, the events are instead forwarded to the underlaying (the white part of the window) view.
So,
I finally managed to solve this puzzle and the solution is divided into two
parts. The problem was, as you way recall, that the click events did not travel to the
buttons that were(visible) outside the UIScrollView. It turned out that the clicks were captured by the underlying view and that it is possible to manipulate their way to finding their target by bending the rules a bit regarding who got hit and thereby tricking the events to get passed where you want them. Not really sure if this is how it should be done but it solved my problem.. . :)
1) First one must override the following method in the bottom view
so that it returns the scrollview instead of itself when appropriate.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
if (view == self)
return [self scrollView];
return view;
}
2) The scrollView must override TWO methods to hand over the clicks to its contained objects.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
// Always return us.
return view ;
}
and
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// We want EVERYTHING!
return YES;
}
Thanks a lot for you comments and willingness to help.
I ho
Inspired by the answer #tommys mentioned, it turns out that by overriding the hinTest method of a UIView and return the scrollView instead, you actually can detach the swiping of this UIView to the scrollView.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
// Doing this make you detached the swiping to the scrollView
if (view == self)
return [self scrollView];
return view;
}
So this UIView is acting like an extension scroll area of the scrollView, the idea is here. If you make the UIView mask over the scrollView and same size of the window, then swiping anywhere inside the window makes the scrollView scroll.
Here is the example, ExtensionScrollArea
Here's my version:
hit test in container
- (UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if ( CGRectContainsPoint( self.frame, point ) && ! self.hidden ) // <-- *
{
if ( ! CGRectContainsPoint( scrollView.frame, point ) )
return scrollView;
}
return [super hitTest:point withEvent:event];
}
(*) This marked line is important if you are moving about or otherwise hiding your view, for instance if you have multiple views, each with their own scrollviews. If you don't have this line, you may be directing all your touches to an off-screen scrollview!
override in scrollview
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
return YES;
}
(in the hitTest of the container, you can exclude additional frames within the if statement for default behaviour) :)

Resources