Reputation: 850
I have three UIViews
:
B1
and B2
are subviews of A
.
B1
and B2
may have one or more subviews.
I want that only one view can be touched at the same time.
For example:
The user touches B1
(or one of its subviews) with the first finger, and then, without releasing it, touches B2
(or one of its subviews) with the second finger. Now, B1
should receive all events in its bounds, but not B2
. Same thing the other way around.
Is this possible to accomplish without subclassing any of these views?
If not, what's the easiest/cleanest way to do it?
Edit:
About exclusiveTouch
and multipleTouchEnabled
:
These properties are not valid for my problem.
multipleTouchEnabled
: Only determines whether or not multi-touch-events are delivered in the same view. So disabling multi-touch on view A (which is the default anyways) will not block view B1 and B2 from receiving touch events at the same time.
exclusiveTouch
: Doesn't work either. As far as I have understood, exclusiveTouch
only works inside the frame of the view. So setting exclusiveTouch
on view B1 and B2 has no effect.
See these two stackoverflows for reference:
Why is UIView exclusiveTouch property not blocking?
Why doesn't UIView.exclusiveTouch work?
Upvotes: 1
Views: 1547
Reputation: 850
I believe this is not possible without subclassing. If anyone can prove me wrong, I'd be happy to see their solution. Anyways, this is how I solved it:
First, I subclass view A
, I call the subclass ParentView
. ParentView needs pointers to B1
and B2
. I also add an additional pointer that is later used to store the currently selected view. Then, I override hitTest
:
@interface ParentView : UIView
@property (nonatomic, strong) UIView* viewB1;
@property (nonatomic, strong) UIView* viewB2;
@property (nonatomic, strong) UIView* currentlyTouchedView;
@end
@implementation ParentView
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.currentlyTouchedView == nil) {
if (CGRectContainsPoint(self.viewB1.frame, point)) {
self.currentlyTouchedView = viewB1;
}else if(CGRectContainsPoint(self.viewB2.frame, point)) {
self.currentlyTouchedView = viewB2;
}
}else {
if (!CGRectContainsPoint(self.currentlyTouchedView.frame, point)) {
return nil;
}
}
return [super hitTest:point withEvent:event];
}
@end
So, what this code does is, it stores the view that is first touched. If one is already stored, it returns nil on hitTest
, which eats up any additional touch events.
Now, where does currentlyTouchedView
get set back to nil? For that, I subclass UIWindow
and override sendEvent
to check if all touches have ended:
@implementation EventOverrideWindow
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
BOOL allTouchesEnded = YES;
for (UITouch* touch in event.allTouches) {
if (touch.phase != UITouchPhaseCancelled && touch.phase != UITouchPhaseEnded) {
allTouchesEnded = NO;
break;
}
}
if (allTouchesEnded) {
ParentView* parentView = ((AppDelegate*)[UIApplication sharedApplication].delegate).parentView;
parentView.currentlyTouchedView = nil;
}
}
@end
I know this is not a very elegant solution and I'd appreciate if someone has a better idea.
Upvotes: 0
Reputation: 42109
Try setting both exclusiveTouch
and multipleTouchEnabled
to YES
for B1 and B2.
Edit: I'm assuming from the wording “B1 should receive all events in its bounds” that you want to receive the touches directly in B1 & B2, not their subviews. If you want to restrict the touches to the B1/B2 bounds but handle them in the subviews I think you need to subclass B1 and B2, or A.
Edit 2: If it's the subviews of B1 and B2 that are interactive, you would need to set these properties to YES
on all interactive subviews (recursively). But this also makes all subviews mutually exclusive to touch, not just between B1 and B2.
Upvotes: 1
Reputation: 19281
When B1 or B2 is touched, set exclusiveTouch
to YES
for that view and then set it back to NO
again when the touches end. This means only the exclusive view will receive touches.
If you want to get touches outside the exclusive view's bounds you can store a reference to the view somewhere when you make it exclusive (myExclusiveView
), then override hitTest:withEvent:
for view A (or any superview) and have it return [myExclusiveView hitTest:withEvent:]
.
Upvotes: 0