Can I create a totally transparent UIView that receives touches? - ios

I want to create a totally transparent UIView overlay (and it has subviews) to receives touches. I could set the alpha to a low value (like 0.02) to get an approximate effect.
But I wonder is it possible for a alpha == 0 UIView to receives touches, through other UIView configs?

You can accomplish this by overriding the hitTest:withEvent: method in the class of your fully transparent view, like:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return self;
}
The implementation of hitTest:withEvent: doesn't have to be that simple, of course. The point is that you can cause even a fully transparent view to be touchable as long as something returns that view from hitTest:withEvent:.
Do note, however, that screwing around with hitTest:withEvent: is an easy way to create some very weird bugs. Use this method with caution.

The better way to do this is to set the background colour:
UIView *view = ...;
view.backgroundColor = [UIColor clearColor];
Add a UITapGestureRecognizer to this to hook up a selector to respond to tapping.

Related

UIScrollview delaysContentTouches issue

I have UIScrollView loaded with UIButtons and on UIButton action I have highlighted UIImage of each UIButton.
If I don't set delaysContentTouches as NO then highlighted UIImage of UIButton will not shown if I touch up UIButton very fast. After I set delaysContentTouches property as NO then only UIButton highlighted UIImage is shown.
Now after setting delaysContentTouches property as NO for UIScrollView. I can not scroll my UIScrollView by dragging on the UIButtons. Now how can I resolve this issue.
Please give me an advise.
Thanks in advance.
Here's what works for me. Subclass UIScrollView, and implement only this method:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
return YES;
}
Then set delaysContentTouches = NO;
Voila! Works just like the home screen: Highlights buttons immediately, but still allows scrolling :)
I found that in iOS 8, the UIScrollView's underlying UIPanGestureRecognizer is not respecting the UIScrollView's delaysContentTouches property. I consider this an iOS 8 bug. Here's my workaround:
theScrollView.panGestureRecognizer.delaysTouchesBegan = theScrollView.delaysContentTouches
OK I have resolved by implementing below method :
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
NSLog(#"touchesShouldCancelInContentView");
if ([view isKindOfClass:[UIButton class]])
return NO;
else
return YES;
}
Unable to find a satisfactory solution online so far (and it seems to be that Apple is ignoring the issue). Found a thread on Apple's developer forum with some suggestions in there that may help: UIScrollView: 'delaysContentTouches' ignored
I was able to use the workaround from this link. To summarize the workaround (I'm para-quoting here):
UIEvent objects contain a time stamp.
You can record the time stamp at the time of touchesBegan on your
embedded subview.
In touchesMoved of scrollView's subview, look at the time stamp and
location again.
If the touch has not moved very far and more than, say, 0.1 seconds
have passed, you can assume the user touched the subview and then
delayed movement.
In this case, the UIScrollView will have decided, independently, that
this is NOT a scrolling action even though it will never tell you
that.
So, you can have a local state variable to flag that this condition of
delayed movement occurred and process events received by the subview.
Here's my code:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// store the timestamp
_beginDragTimeStamp = event.timestamp;
// your embedded subview's touches begin code
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
// compare and ignore drag if time passed between tap and drag is less than 0.5s
if(event.timestamp - _beginDragTimeStamp < 0.5) return;
// your drag code
}
I had same issue & same hierarchy of the views, With latest sdk , just use it :
Setting delaysContentTouches to NO for UIButton in the same UITableViewCell.
self.scrollview.delaysContentTouches = NO
Create a subclass of the UIScrollView (or UITableView, or UICollectionView, or any other UIScrollView subclass that you use).
Implement the below method:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
if ([view isKindOfClass:UIButton.class]) {
return YES;
}
return [super touchesShouldCancelInContentView:view];
}
Set this subclass at xib/storyboard as a "Custom Class" class if you use the interface builder.
Unselect Delay Touch Down in a xib or set delaysContentTouches = NO in code.

iPad: How can I click buttons underneath transparent portions of a UIView

I am subclassing a view which is the same size as my main ViewController (1024x768). This subview has a transparent background and contains buttons that are sized 50w X 50h and are positioned dynamically.
My issue is that I need to interact with content and buttons that exist beneath this view but this subview is blocking that interaction.
I've seen some posts address a similar problem, but I am unclear of the actual usage.
-pointInside:withEvent: is how iOS asks if a touch is within a particular view. If a view returns YES, iOS calls -hitTest:withEvent: to determine the particular subview of that view that was touched. That method will return self if there are no subviews at the location of that touch. So you can pass any touches that aren't on subviews back to views behind this one by implementing -pointInside:withEvent: like this:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
return ([self hitTest:point withEvent:event] != self);
}
If you need to catch some touches that aren't on subviews, your implementation will be more complicated, but this method is still the right place to tell iOS where your view is and accepts touch events.
Did you try to set userInteractionEnabled to YES or NO?
If all else fails you can bring those subviews to the front programmatically using
[self.view bringSubviewToFront:buttonToClick];

UIView 'falling rain' transparent overlay that can be clicked through?

I'd like to create a UIView which will animate falling rain, which should be above all other views, at the very front, but transparent, and it shouldn't register taps or interaction at all, so tapping the UI behind it should behave exactly as it does now, before I've implemented it. It'd act as an overlay, and nothing more.
Is this as simple as setting UserInteractionEnabled to NO, or is there more to it? Do I need to subclass UIView, or override something, etc.?
Yes, set userInteractionEnabled = NO; No need to subclass UIView. e.g.:
UIView *overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
overlay.backgroundColor = [UIColor colorWithWhite:0 alpha:.5];
[self.view addSubview:overlay];
You can make use of hittest after adding the transparent subview as #zenith has explained.
The implementation of hitTest:withEvent: in UIResponder does the following:
It calls pointInside:withEvent: of self
If the return is NO, hitTest:withEvent: returns nil. the end of the story.
If the return is YES, it sends hitTest:withEvent: messages to its subviews. it starts from the top-level subview, and continues to other views until a subview returns a non-nil object, or all subviews receive the message.
If a subview returns a non-nil object in the first time, the first hitTest:withEvent: returns that object. the end of the story.
If no subview returns a non-nil object, the first hitTest:withEvent: returns self
This process repeats recursively, so normally the leaf view of the view hierarchy is returned eventually.
However, you might override hitTest:withEvent to do something differently. In many cases, overriding pointInside:withEvent: is simpler and still provides enough options to tweak event handling in your application.

Stopping MapView's subviews from propagating touches

When I add a subview to a MKMapView or to any of it's descendent views, touches will pass right through and affect the map.
This question is ultimately used to explain a bug that was fixed for my custom callout which adds a view to an annotation view. The callout is not to be dismissed when the callout is touched and this question is in reference to that. But for the case brevity I'll use a smaller example that demonstrates the problem.
Setting exclusiveTouch and userInteractionEnabled doesn't do anything. BTW.
Let's take a UIView and attach it to the mapView
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]
view.backgroundColor = [UIColor blackColor];
[mapView addSubview:view];
And let's add a gesture to the map as well
[mapView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)]];
And just have handleLongPress print something NSLog(#"longpress"); Now run the program and observe how long pressing the black uiview will cause the longPress to be triggered. If we add an annotation that shows a callout and select it, touching the black uiview will dismiss it. And if we scroll the map so that the annotation is under the black uiview, we'll be able to select the annotation. Now here's my solution. We tag the view that we're going to add to mapview with SOME_TAG and overwrite the hitTest method for MKMapView
#interface MKMapView(HackySolution)
#end
#implementation MKMapView(HackySolution)
- (UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
// block touch propagation
if(view.tag == SOME_TAG){
view = nil;
}
return view;
}
#end
Now here's the deal, I may have solved my problem, but I really don't know why touches from the UIView are being passed right through onto the map. It doesn't make sense for a touch to a subview to be passed onto it's parentView. Maybe I'm not understanding how touches work. I mean if I place a button on a normal UIView and place another uiview on top of it, completely obscuring it, the button wouldn't get the touch right? I'm starting to second guess myself. :/
I can only guess that MKMapView is wired to do this, but I'm unsure. I thought there was a scroll view somewhere within MKMapView, but I ran through all the subviews and all the descendents and didn't find anything.
Is there a better way to solve this problem or is this the best it gets?
Any insight into the situation would be greatly appreciated.

Touch event handled by multiple views

I have a subclass of UIView on top of a UITableView. I am using the UITableView to display some data and, at the same time, I would like to overlay an animation that follows the finger (for instance, leaving a trail).
If I get it right, I need the touch events to be handled both by the UIView subclass and the UITableView. How can I do that?
Is it possible to have, ie, touchesMoved being triggered on the UIView subclass and then on UITableView?
Thank you so much for any help.
The way I have solved this problem is in a way that is not that clean, but it works. Please let me know if there's a better way to do this.
I have overridden hitTest for my custom UIView so that it directs touches to the UITableView underneath. Then in the UITableView I am handling the gestures through touchesBegan, touchesMoved, etc. There I am also calling touchesBegan on the UIView.
In this way touches are handled by two views.
The reason why I am not doing the other way around (having UIView's touchesBegan calling UITableView's touchesBegan) is that gestures recognizers on the UITableView would not work.
UIView subclass' hitTest
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// tview is the UITableView subclass instance
CGPoint tViewHit = [tView convertPoint:point fromView:self];
if ([tView pointInside:tViewHit withEvent:event]) return tView;
return [super hitTest:point withEvent:event];
}
UITableView subclass's touchesBegan
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:touch.view];
// ....
// view is the UIView's subclass instance
[view touchesBegan:touches withEvent:event];
}
No, you cann't do it implicity. Event Delivery chapter says
The window object uses hit-testing and the responder chain to find the
view to receive the touch event. In hit-testing, a window calls
hitTest:withEvent: on the top-most view of the view hierarchy; this
method proceeds by recursively calling pointInside:withEvent: on each
view in the view hierarchy that returns YES, proceeding down the
hierarchy until it finds the subview within whose bounds the touch
took place. That view becomes the hit-test view.
So, when window finds touched view it returns YES. Only one view can handle touches at the current moment.
But if you need to handle event for UITableView then handle it for UIView! You can convert touched point to required coordinates with – convertPoint, – convertRect functions, add subview to UITableView and move it depends on coordinate, and a lot of another things.
UITableView relays unhandled touch events to UIView. (Google "responder chain")
UITableView Documentation
So, you can handle your touch events in UIView only. So. In your UIView
touchesstart - do initialization stuff
touchesmove - draw tail on UIView (Use timers/delayedresponse to desable points so that it would look like a trail)
touchesend - do remaining stuff
Hope this helps.

Resources