How are targets getting triggered in UIButton? - ios

I have a UIButton in a View and over that button I have a UILabel (TTTAttributedLabel). (https://github.com/mattt/TTTAttributedLabel) (Though, this is not a direct question related to TTTAttributedLabel custom class)
This UILabel performs some actions with touch and If doesn't like touch coordinates it forwards them to super with the followings in corresponding methods:
[super touchesBegan:touches withEvent:event];
[super touchesMoved:touches withEvent:event];
[super touchesEnded:touches withEvent:event];
[super touchesCancelled:touches withEvent:event];
I see that the button below is being pressed in that case. I also see that all of those methods in UIButton is getting called perfectly. I also log UITouch objects to see if they match.
The problem is, the action set to UIButton is not getting called (even for UIControlEventTouchDown). What does trigger those actions? I thought that a matching touchesBegan with touchesEnded should have called the selector added for target action to that button.

Touches trigger the control events, and I would say your button does not receive touches simply because there is a label over a button.
There are two ways you could go around this. First, and probably the preferred way is to use the atributedTitle proprety of a UIButton instead of mashing an additional label where it's not necessary:
https://developer.apple.com/library/ios/documentation/uikit/reference/UIButton_Class/UIButton/UIButton.html#//apple_ref/occ/instm/UIButton/attributedTitleForState:
The other way is to delve deeper into UIControl, possibly subclass it, and then handle the touch events on a bit of a finer scale, although this is far more complex than the solution above:
https://developer.apple.com/library/ios/documentation/uikit/reference/uicontrol_class/reference/reference.html

Related

iOS UISlider values mismatched

I have implemented Vertical Slider in one of my App which extends UISlider. when scrolling is ended/done I am sending commands to the server with the Slider values. Sometimes when I scroll fast up/down and release then slider values are getting mismatched before sending command and after sending command.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Value of Slider before sending command=%f",self.value);
[self sendCommand]; // Here value is something else
[super touchesEnded:touches withEvent:event];
NSLog(#"Slider value after sending command=%f",self.value); // Here value changed
}
But if I place super call before sending command then everything works fine. Please explain if anyone knows why this is happening.
[super touchesEnded:touches withEvent:event];
More interesting fact is if I don't call super then also everything works well.
This is because the method [super touchesEnded:touches withEvent:event]; is where the slider updates it's value, based on the interaction. So the value is out of date before super is called.
A call to super should usually be the first line of an overridden method.
Even though the name touchesEnded: implies that the slider is done updating, it still might update the value depending on how the user lifted up their finger. This makes sense if you think about a user sliding quickly and then lifting up their finger—they probably expect it to go slightly farther in that direction than their finger actually went.
UIKit calls this method when a finger or Apple Pencil is no longer
touching the screen. Many UIKit classes override this method and use
it to clean up state involved in the handling of the corresponding
touch events. The default implementation of this method forwards the
message up the responder chain. When creating your own subclasses,
call super to forward any events that you do not handle yourself. For
example, [super touchesEnded: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, even if your implementations do nothing.

UISegmentedControl: how to detect click on current segment?

is there a way to detect second click on a segment in UISegmentedControl? I found:
Detect second click on a segment
however, it is stated that:
If you set a segmented control to have a momentary style, a segment doesn’t show itself as selected (blue background) when the user touches it. The disclosure button is always momentary and doesn’t affect the actual selection.
Is there a way to detect second click as well as trigger the selection action and show the segment as selected?
If there is no straight forward way to do it, what I was thinking, is that I first have the momentary flag set to YES, then upon each click, manually update the selection state, but then I also need to update/deselect other segments.
Thanks
The solution is to have a custom subclass of UISegmentedControl and check it yourself like this.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
current = self.selectedSegmentIndex;
[super touchesBegan:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
if (current == self.selectedSegmentIndex)
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
I had an other solution all in touchesBegan, but it's not working anymore in iOS 7. There is also other solution on Stack Overflow that are not working in iOS 6 and greater.
To make a particular segment click-able again is not possible, but you can reset the whole segmentControl using UISegmentedControlNoSegment.
[self.segmentCtrlOutlet setSelectedSegmentIndex:UISegmentedControlNoSegment];
what you have to do is put the above code in the place where that code executes when you click on a particular segment of UISegmentedControl.
for eg. In my project when I click on a segment, UIPopoverController opens up and inside it I have UIPicker, so I use the above code in UIPicker delegate method "didSelectRow"

touchesBegan: withEvent: is not called when tapped on a UIButton

What I want to implement is column matching type functionality. I have three buttons on right column and three on left column with some info on them. I want to draw path from one button on right side to any of the button on left side by dragging finger.
I would use UIBezierPath path to draw path, and for that definitely a start and end point is needed.
Now the issue is how can I trigger touchesBegan, touchesMoved and touchesEnded methods by tapping on buttons so that I can get start and end points.
Another option that I am thinking about is to cover all the buttons with an invisible UIView, and somehow check if the point touched of this overlay view lies in any of the button frames.
BTW I can replace these buttons with simple UIImageView as well. I added buttons just for the sake of getting touch points.
Any help.
Create a subclass of UIButton and add this...
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[self.nextResponder touchesBegan:touches withEvent:event];
}
This will get you your touches within the button.
Uncheck User Interaction Enabled checkbox of UIButton. You will get call for touchesbegan method on that button.

Trigger touchesEnded when dragging outside the UIView

I have a UIView subclass where I handle touches using touchesBegan, touchesMoved, touchesEnded.
I noticed that when I start a touch inside and then drag outside the UIView touchesEnded is not triggered, is there a way to have it called when I'm dragging outside the UIView frame?
Thanks
Use touchesCancelled instead.
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
}
Pssibly it is calling the - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
Please check UIResponder Class Reference
touchesCancelled:withEvent:
Sent to the receiver when a system event (such as a low-memory
warning) cancels a touch event.
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
Parameters
touches
A set of UITouch instances that represent the touches for the ending phase of the event represented by event. event
An object representing the event to which the touches belong.
Discussion
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
When an object receives a touchesCancelled:withEvent: message it
should clean up any state information that was established in its
touchesBegan:withEvent: implementation.
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 touchesCancelled: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. Availability
Available in iOS 2.0 and later.
You should still be getting touchesMoved in your viewController even you are outside of your view's frame. I would add array to your custom UIView which would keep touch objects that this view is assigned to and implement the following logic:
When you receive touchesBegan - add touch object to array for given UIView where touch coordinates match (you may have to iterate over all your subviews and match coordinates with frame to find the right one)
When you receive touchesMoved - remove touch from UIView's array for previous location and add it to UIView for current location if any
When you receive touchesEnded & touchesCancelled - remove touches from all UIViews arrays
When you remove or add touch object from your custom UIView array you may call your delegate methods (implement observer pattern) because at that point you know if it's really pressed or unpressed event. Keep in mind you may have many touch objects in one array so before calling your delegate check whether you added first object or deleted last one because that's the only case you should call your delegate.
I have implemented such solution in my last game, but instead of UIViews I had virtual buttons.

Why Does The iPad Become Nonresponsive After UIGestureRecognizer is Added to UIWindow?

Background: I need to look at every UITouch that happens in an application and see if the UITouch is NOT inside a certain UIImageView.
WildcardGestureRecognizer looked very promising and I tried it. (Code is here):
How to intercept touches events on a MKMapView or UIWebView objects?
It worked great for a sandbox/quickly-created application. However that application didn't reflect the complexity that the actual target project has. The target project has a Table View Controller and more.
After adding the WildcardGestureRecognizer to the more involved iPad application, I see that none of the other controls work after the gesture recognizer is added and one click happens.
Here's some code where I was playing with the idea. Again, the sandbox code does not yet have controls on it (such as a Table View Controller or even a UIButton) to see if they work after adding the gesture recognizer to the UIWindow.
Should I pick something other than the UIWindow to add the gesture recognizer to or am I going to run into the same problem regardless? Is there a better way?
sandbox code: https://github.com/finneycanhelp/GestureKata
You might want to try another approach: create a UIWindow subclass, use that in your XIB and override hitTest:withEvent:. It returns the view that has been "selected" to receive the touch events.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *v;
v = [super hitTest:point withEvent:event];
if (v == myImageView) {
// Do something. Maybe return nil to prevent the touch from getting
// sent to the image view.
}
return v;
}
Overriding this method can also be helpful when debugging.

Resources