UITouch not releasing view - ios

I have a custom view which is not being deallocated. I dismiss controller on close button pressed. Now if I only press the button the view is deallocated alright. But If press button with one finger with other finger touching the view its not deallocated on dismiss but on the next touch event.
Its UITouch which is keeping the reference of my view and not releasing it. How can I fix this?
Here is my code for my close action:
- (IBAction)closePressed:(UIButton *)sender {
NSLog(#"Close pressed");
if (self.loader)
[self.loader cancelJsonLoading];
[self.plView quit];
[self dismissViewControllerAnimated:YES completion:nil];
}

Did you try to call:
[self.view resignFirstResponder];
That should cancel all pending UITouches.
If this doesn't work, you can keep trace of your touches:
define a NSMutableSet where you store current touches:
NSMutableSet *_currentTouches;
in your init():
_currentTouches = [[NSMutableSet alloc] init];
And implement:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super.touchesBegan:touches withEvent:event];
[_currentTouches unionSet:touches]; // record new touches
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super.touchesEnded:touches withEvent:event];
[_currentTouches minusSet:touches]; // remove ended touches
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super.touchesEnded:touches withEvent:event];
[_currentTouches minusSet:touches]; // remove cancelled touches
}
Then, when you need to clean your touches (when you release your view for instance):
- (void)cleanCurrentTouches {
self touchesCancelled:_currentTouches withEvent:nil];
_currentTouchesremoveAllObjects];
}
It is, I think, a bit hacky, but the doc says:
When an object receives a touchesCancelled:withEvent: message it
should clean up any state information that was established in its
touchesBegan:withEvent: implementation.

Related

Prevent parent view controller from receiving UITouch in child controller

So, in my app, then a map annotation is pressed, It reveals a modal view controller. Then when an element that modal controller is pressed, I open another, smaller controller. In both controllers, I have implemented the method
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
if (!CGRectContainsPoint([self.view viewWithTag:21].frame, touchPoint)) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
Here is an image that shows my layout:
My problem is that when I touch in yellow circle one, the second modal dismisses, and after that, the first modal dismisses. SO, how can I prevent the first modal from receiving the touch.
PS: both modals are transparent UIViews, with smaller views inside them to display the content.
Obviously the event is passed through the view controller to the next responder.
In the class reference of UIResponder.
The default implementation of this method does nothing. However
immediate UIKit subclasses of UIResponder, particularly UIView,
forward the message up the responder chain. To forward the message to
the next responder, send the message to super (the superclass
implementation); do not send the message directly to the next
responder. For example,
[super touchesMoved:touches withEvent:event];
If you override this method without calling super (a common use
pattern), you must also override the other methods for handling touch
events, if only as stub (empty) implementations.
So if you don't want the event to forward to the next responder, you should override the other methods.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
if (!CGRectContainsPoint([self.view viewWithTag:21].frame, touchPoint)) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}

UIControl tracking touch that started on a different UIControl

I am making a custom iOS keyboard and have a UIControl subclass to represent my button. I am trying to get the same behaviour as the normal iOS keyboard:
User begins touch on one button
User drags over other buttons (need to detect this so they can highlight/dehighlight accordingly)
Register the actual keyboard "press" when the user lifts their finger; that is, the touch ends
I am testing using the touch tracking methods like this:
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super beginTrackingWithTouch:touch withEvent:event];
NSLog(#"Begin for %#", [self label]);
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super continueTrackingWithTouch:touch withEvent:event];
NSLog(#"Continue for %#", [self label]);
return YES;
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super endTrackingWithTouch:touch withEvent:event];
NSLog(#"End for %#", [self label]);
}
These methods are all called, except they are only ever called on the UIControl where the touch began.
What is the best way to recognise touches coming and going across all my buttons? Do I have to do it all via the parent view?
I'll select a better answer if offered... but in case anybody finds this by search, I've managed to get what I need this way:
Set userInteractionEnabled to NO for the button class (UIControl subclass)
Don't override any touch methods in the button class
Implement touchesBegan:withEvent:, touchesMoved:withEvent: and touchesEnded:withEvent: in the view controller
On each event, extract the location from the UITouch object
Iterate over all of the button subviews and find the one containing the touch location:
- (MyButton *)buttonForTouch:(UITouch *)touch
{
CGPoint windowLocation = [touch locationInView:keyboardView];
for (MyButton *button in buttons) {
if (CGRectContainsPoint([button frame], windowLocation)) {
return button;
}
}
return nil;
}
Having determined which button the user is interacting with, make the view controller send messages to the relevant buttons to adjust their appearance
If appropriate, keep a reference to the UITouch instance in touchesBegan:withEvent: so you can be sure that you're tracking the same one in the other methods
I think that you should have a single big UIControl which has different subviews (like UIButton) but tracks touches by itself like you did already but finds out which subview to highlight depending on the touch position.

iOS delay touchUpInside button event

I have some custom animations in my UIButton subclass. I override some inner methods for starting and ending animation
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
//there is my start animation
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
//there is my end animation
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
//there is my end animation
}
There is almost all good. All my buttons make some actions on event UIControlEventTouchUpInside. And there is my problem. Methods touchesEnded and touchesCancelled are called only after my app handle UIControlEventTouchUpInside event. So animations in button looks not so nice like I want.
So question is: can I delay UIControlEventTouchUpInside event on some ms and firstly handle touchesEnded method of my button for smooth animation?

Programmatically initiate hitTest:WithEvent after gesture recognizer was canceled and return a different UIView to respond to events

I am overriding hitTest:withEvent to return self (the bottom most view)-
When returning self - my view will respond to touch events in turn initiating gesture recognizers.
If a gesture is canceled or some set of conditions happened - I want to manually initiate hitTest:withEvent and then return a different view to take care of the same sequence of events/touches that occurred. This is necessary as a gesture recognizer only initiates after hitTest:withEvent returns the gestures view and its state changed to began.
I am not sure how to do this - I thought about manually calling on my subviews
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
}
But I don't have the event parameter (The gesture received it)
I think this could not be done, pass touch event to UIGestureRecognizer is private API. But you can pass touch event the bottom most view received to any view you like and do your own gesture recognize.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView* selectView = [self _findMatchView];
// maybe convert touches to selectView coordinate
[selectView handleTouchBegan:touches withEvent:event];
}

UIScrollView inside a UITableViewCell - No didSelect call

I have a tableviewCell, where the user can scroll horizontally. Since the scrollView covers nearly the whole cell, the tableView method didSelectRow gets not called if the user clicks the cell.
So I thought, I could pass the touch event of the UIScrollView to the cell, but still the didSelectRow doesnt gets called.
I subclassed UIScrollView to pass the touch event only, if the touch was not a drag:
- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
NSLog(#"touch scroll");
// If not dragging, send event to next responder
if (!self.dragging)
[self.superview touchesEnded: touches withEvent:event];
else
[super touchesEnded: touches withEvent: event];
}
Any ideas on how to pass the click to the table, to get the delegate-methods called and keep the scrolling inside the scrollview?
You can actually do this without subclassing UIScrollView. Whether you have a custom cell, or are setting properties in cellForRowAtIndexPath in the UITableView, you can do the following:
[cell.contentView addSubview:yourScrollView];
yourScrollView.userInteractionEnabled = NO;
[cell.contentView addGestureRecognizer:yourScrollView.panGestureRecognizer];
The reason you can do this is because scrollView has its own panGestureRecognizer that's accessible to the programmer. So, just adding it to the cell's view will trigger the scrollview's gesture delegates.
The only drawback of this approach is that subviews of the scroll view are unable to receive any touch input. If you need this you will have to chose a different approach.
I just encountered the same problem.
In your subclass make sure to include the full set of methods:
-(void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging)
[self.superview touchesCancelled: touches withEvent:event];
else
[super touchesCancelled: touches withEvent: event];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging)
[self.superview touchesMoved: touches withEvent:event];
else
[super touchesMoved: touches withEvent: event];
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging)
[self.superview touchesBegan: touches withEvent:event];
else
[super touchesBegan: touches withEvent: event];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging)
[self.superview touchesEnded: touches withEvent:event];
else
[super touchesEnded: touches withEvent: event];
}
The selected answer is correct, but I updated the code based on a bug I was getting.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.dragging) {
[super touchesMoved:touches withEvent:event];
} else {
if ([self.delegate isKindOfClass:[UITableViewCell class]]) {
[(UITableViewCell *)self.delegate touchesCancelled:touches withEvent:event];
}
[self.superview touchesMoved:touches withEvent:event];
}
}
If your self.delegate is not the UITableViewCell, than replace that property with a property to your cell.
The cell needs to retrieve the cancel touch event during movement to prevent the undesired results. It can be easily reproducible as follows.
Highlight the cell (assuming the scroll view is over the whole cell, if not highlight the scroll view)
While the cell is highlighted, drag the table view
Select any other cell and now the previously highlighted cell will retrieve the didSelectCell state
Another point to mention is that order matters! If the self.delegate is not called before the self.superview then the highlighted state wont happen.
Swift 3
scrollView.isUserInteractionEnabled = false
contentView.addGestureRecognizer(scrollView.panGestureRecognizer)
try set this
_scrollView.canCancelContentTouches = NO
also, it is bad to partially forward touch events

Resources