I've implemented a really simple custom UISwitch that uses touches events like:
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
I can use this control in my views without problem. The Control simulates a UISwitch, so it can change value through a drag or through a tap.
My problem is that I can't let this Control work inside a Cell of a UITableView. Only the single tap seems to work (note that I'm not using gesture... but the events that I've previously listed) but I can't "swipe" the switch handle.
I instantiate the controller within the tableView:cellForRowAtIndexPath method adding the control as subview of the cell contentView:
[cell.contentView addSubview:customSwitch];
I suppose that it is something related with the fact that UITableView is a UIScrollView and I think that touches events get somehow "stolen" by it.
// EDIT --------------------
Here is the code related with the Touch events.
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super beginTrackingWithTouch:touch withEvent:event];
self.dragged = NO;
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
if (!self.enabled) {
return NO;
}
[super continueTrackingWithTouch:touch withEvent:event];
self.dragged = YES;
CGPoint touchPoint = [touch locationInView:self];
float centerPoint = FIXED_WIDTH / 2.0;
[self centerOn:(touchPoint.x > centerPoint) animated:YES completion:nil];
return YES;
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super endTrackingWithTouch:touch withEvent:event];
CGPoint touchPoint = [touch locationInView:self];
BOOL currentOn = self.on;
BOOL nextOn;
if (self.dragged) {
float centerPoint = FIXED_WIDTH / 2.0;
nextOn = (touchPoint.x > centerPoint);
[self setOn:nextOn animated:NO];
}else{
nextOn = !self.on;
[self setOn:nextOn animated:YES];
}
self.dragged = NO;
if (currentOn != nextOn) {
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
How can I make the control work inside a Cell without interfering with the UIScrollView/UITableView?
You need to implement your custom switch using gestures instead of trying to directly process events.
The gesture recognizers work behind the scenes to coordinate their event processing which, for example, is why a UIScrollView can work inside of another UIScrollView. You need that coordination for the UITableView (really a UIScrollView) to properly handle swipe gestures within its content.
Search the web for Hardy Macia's UICustomSwitch sample code for a good example of matching the UISwitch behaviors in a custom control.
Did you miss the touchesBegan: and touchesMoved:?
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
[super touchesBegan:touches withEvent:event];
// Get the only touch (multipleTouchEnabled is NO)
UITouch* touch = [touches anyObject];
// Track the touch
[self beginTrackingWithTouch:touch withEvent:event];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
[super touchesMoved:touches withEvent:event];
// Get the only touch (multipleTouchEnabled is NO)
UITouch* touch = [touches anyObject];
// Track the touch
[self continueTrackingWithTouch:touch withEvent:event];
}
Related
In my app I have an UIView on top of UISplitViewController (used "addSubView"). What I wanna do is to let the UIView handle the touch and then redirect it to the parent view so it could also handle it.
I think i have a work around for what you try to do. You'll have to subclass your topView and implement the following two methods:
The first method will handle your touch for the buttomView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint bottomViewHit = [bottomView convertPoint:point fromView:self];
if ([topView pointInside:topViewHit withEvent:event]) {
//manage your bottomView touch here..
return bottomView;
}
return [super hitTest:point withEvent:event];
}
The second method will handle the topView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:touch.view];
// manage your touch for topView here...
[view touchesBegan:touches withEvent:event];
}
I have UIWebView for display the articles. I have UIWebView for displays HTML articles. When user touch some area in UIWebView, UIMenuController will display. Then user select note button it displays UITextView. How to get touch location?
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
// Get the specific point that was touched
CGPoint point = [touch locationInView:wbCont];
NSLog(#"X location: %f", point.x);
NSLog(#"Y Location: %f",point.y);
}
- (void)note:(id)sender {
}
UIWebview is wrapped inside a UIScrollView. Therefore the touch events do not go through to the UIView where your method receives the touch events.
For your UIViewController it should implement UIGestureRecognizerDelegate
Then add a gesture to your webview:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapTest:)];
[tap setDelegate:self];
[self.yourwebview.scrollView addGestureRecognizer:tap];
Next:
- (void)tapTest:(UITapGestureRecognizer *)sender {
NSLog(#"%f %f", [sender locationInView:self.yourwebview].x, [sender locationInView:self.yourwebview].y);
}
Edit:
shouldRecognizeSimultaneouslyWithGestureRecognizer should return YES as well.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Try this :-
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
firstTouch = [touch locationInView:self.view];
NSLog(#" CHECKING CGPOINT %#", NSStringFromCGPoint(firstTouch));
}
works fine for me ;-)
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
//UITouch *touch = [[event touchesForView:textview] anyObject];
CGPoint location = [touch locationInView: wbCont];
CGPoint previousLocation = [touch previousLocationInView:textview];
}
There's only one truly fail-safe way to do this (that I know of):
#interface MyWebView : UIWebView
#end
#implementation MyWebView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if ([self pointInside:point withEvent:event])
{
NSLog(#"Touched inside!");
}
return [super hitTest:point withEvent:event];
}
#end
Web views are extremely complex, so overriding those UIResponder methods or adding gesture recognisers won't work. Unfortunately, hitTest:withEvent: gets called 3 times on each touch, so you'll have to deal with that.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
NSLog(#"X location: %f",touch.view.position.x);
NSLog(#"Y Location: %f",touch.position.y);
}
I have a UITableView within a UIView where the UIView is acting as a pullup view. I would like to drag the view up and down the screen and for the view to move up with my drag.
If I am touching the UITableView part then I would like to just drag up until the UIView reaches some top limit and then for my dragging to start dragging up the UITableView.
If the UIView is at its top limit and I drag down on the UITableView then I would like the UITableView to drag down until it reaches its beginning then for the drag to transfer to the UIView, dragging that down the screen with the UITableView along with it.
Furthermore, the UITableVIew has buttons that need to continue reacting to presses so I cannot simply turn off the user interactivity of the tableview when I want.
Currently I am setting a flag when the UIView is at its top parking position and then using the following methods in an extended UITableView class
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint then = [[touches anyObject] previousLocationInView: self];
CGPoint now = [[touches anyObject] locationInView: self];
//CGFloat deltaY = fabs(then.y - now.y);
if(self.contentOffset.y == 0 && now.y > then.y)
inSlideMode_ = YES;
if(inSlideMode_)
{
[self.nextResponder touchesMoved:touches withEvent:event];
}
else
{
[super touchesMoved:touches withEvent:event];
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint then = [[touches anyObject] previousLocationInView: self];
CGPoint now = [[touches anyObject] locationInView: self];
//CGFloat deltaY = fabs(then.y - now.y);
if(self.contentOffset.y == 0 && now.y > then.y)
inSlideMode_ = YES;
if (inSlideMode_)
{
//forwarding action:
[self.nextResponder touchesBegan:touches withEvent:event];
}
else
{
[super touchesBegan:touches withEvent:event];
}
}
-( void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(inSlideMode_)
{
[self.nextResponder touchesEnded:touches withEvent:event];
}
else
{
[super touchesEnded:touches withEvent:event];
}
previousTouchY_ =UNTOUCHED_Y_POSITION;
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y <= 0)
{
self.scrollEnabled = NO; // if we reach the top of scrolling then should switch tableview into the pullup interactor
self.inSlideMode = YES;
}
}
In the UIView extended class, I am overriding the same methods to handle the dragging and also looking to see whether it is docked to top.
Currently it nearly works but when dragging the UITableView, once it reaches the bottom I need to lift finger and start dragging on it again to get the UIView to be dragged down. Any suggestions??? Thanks
I have two custom uibuttons.
#interface myButton : UIButton
I overwrite several methods like
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// do something
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
[self.nextResponder touchesMoved:touches withEvent:event];
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
if (!CGRectContainsPoint(self.bounds, touchPoint)) {
[self touchesCancelled:touches withEvent:event];
}
return;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// do something
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
return;
}
The thing I want is , when I touch a button, I can keep moving my touch to another button. I tired to call touchesCancelled when the touch is out of the bound of the first button I touched. So I "think" then I move to another button it will be a new touch event. But it doesn't work like this.
Did I do something wrong? Or the touchesCancelled method is not used for like this?
Thanks in advance!
Your suspesions are correct, this is not how touchesCancelled:withEvent: was intended to be used. From the documentation:
This method is invoked when the Cocoa Touch framework receives a system interruption requiring cancellation of the touch event; for this, it generates a UITouch object with a phase of UITouchPhaseCancel. The interruption is something that might cause the application to be no longer active or the view to be removed from the window.
Your responder will receive the touches cancelled event if your user receives an incoming phone call, SMS or their alarm goes off etc. It should be used to clean up any state information that was established in the other touch events.
It seems as though you want to change the responder associated with the touch events mid-touch i.e. when you drag the touch from the bounds of the first button and enter that of the second button it should become the responder recieving the touch events. Unfortunately that is not the way responders are designed to work. Once a UIView is identified as being the responder, as returned in hitTest:withEvent:, this will be the UIView to receive touch events.
A possible workout to achieve what you want would be to handle the touch events (touchesBegan:withEvent:, touchesMoved:withEvent: etc.) in a superview that contains both of your buttons. Then your superview will receive the touch events and you can take action depending on which button's frame you are within:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
CGPoint touchLocation = [touch locationInView:self.view];
UIButton *button;
if (CGRectContainsPoint(self.button1.frame, touchLocation))
{
button = self.button1;
NSLog(#"touchesMoved: First Button");
}
else if (CGRectContainsPoint(self.button2.frame, touchLocation))
{
button = self.button2;
NSLog(#"touchesMoved: Second Button");
}
// Do something with button...
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
CGPoint touchLocation = [touch locationInView:self.view];
UIButton *button;
if (CGRectContainsPoint(self.button1.frame, touchLocation))
{
button = self.button1;
NSLog(#"touchesMoved: First Button");
}
else if (CGRectContainsPoint(self.button2.frame, touchLocation))
{
button = self.button2;
NSLog(#"touchesMoved: Second Button");
}
// Do something with button...
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
CGPoint touchLocation = [touch locationInView:self.view];
UIButton *button;
if (CGRectContainsPoint(self.button1.frame, touchLocation))
{
button = self.button1;
NSLog(#"touchesEnded: First Button");
}
else if (CGRectContainsPoint(self.button2.frame, touchLocation))
{
button = self.button2;
NSLog(#"touchesEnded: Second Button");
}
// Do something with button...
}
}
So I have a Subclass of UIView that is suppose to detect touches. The view detect touches only if the touches started inside the current view. When the touches start outside of the view and they move inside my custom view touchesMoved doesn't get called. Any solution to detect moving touches that have not started in the current view?
#implementation MycustomView
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// This only gets called if touches have started in the current View
}
#end
The following solution worked. I have multiple instances of MyCustomView; as the touches move I want to detect the views that are being touched
I ended up moving touch detection from MyCustomView to its superView, so the following code is no longer in MyCustomView class:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.contentView];
for (UIView *view in self.contentView.subviews)
{
if ([view isKindOfClass:[MyCustomView class]] &&
CGRectContainsPoint(view.frame, touchLocation))
{
}
}
}
this should fix it:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
for (UIView* subView in self.subviews)
{
if([subView pointInside:[self convertPoint:touch toView:subView] withEvent:event])
{
//do your code here
}
}
}
One way to do it (though there might be others) is to disable user interaction for the subviews and make their parent view track the movement (use the hitTest method to figure out which view the touch is currently over).
Try this....
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for(UITouch *touch in touches)
{
CGPoint touchPointFirstBtn = [touch locationInView:self.ChordView];
if(CGRectContainsPoint(_btnC.frame, touchPointFirstBtn))
{
if (!_btnC.isHighlighted)
{
if(!Boolean)
{
title = #"C";
[_tlbView reloadData];
NSLog(#"%#",#"touches C");
}
[_btnC setHighlighted:YES];
Boolean = YES;
}
}
else
{
[_btnC setHighlighted:NO];
Boolean = NO;
}
}