I am writing an app for drawing.
My main question is: How do I get the best performance from touch events?
Is there a difference between UIGestureRecognizer and UITouch Delegates?
Are there different refresh rates or sth.?
Best
Philipp
For a drawing app you should use UITouch delegates because it will be faster then other UIGestureRecognizer. The reason is whenever a tap event occurs in that case the UIEvent of tap action is traversed thorough the UIResponder chain and transmitted to the UIResponder which is responding to the tapped event. So if you'll add a UIGesture on top of a view then there will be an extra machine cycle to check the UIGesture in responder chain which is not worthy for a drawing apps cause these apps should be much faster in responding to taps.
All you need is to set the userInteractionEnabled property of the view where you want to draw.
Related
Quick question: Is there an easy way to hit test in a UIView with the touch information? I have one UIView that I need pencil to respond to, I have another that I need the finger to respond to (PDFView) I want a way to forward the touch to each view based upon the touch type. Thoughts?
You can use UIResponder's method touchesBegan(_:with:): https://developer.apple.com/documentation/uikit/uiresponder/1621142-touchesbegan
or UITapGestureRecognizer: https://developer.apple.com/documentation/uikit/uigesturerecognizer/1620009-touchesbegan
https://developer.apple.com/documentation/uikit/uigesturerecognizerdelegate/1624214-gesturerecognizer
Information about touch type you can take from UITouch object: https://developer.apple.com/documentation/uikit/uitouch/touchtype
After rearranging the structure of my UIViews I seem to have introduced a delay in drawing (users can draw on the screen with their finger). Before the onset of drawing was negligible, but now there is a noticeable latency between the initial movement of the finger and the drawing of the line. As I keep drawing the latency seems to disappear. So it is possible the initial touch event is delayed somewhere.
My question is not how to solve this specific instance, but in the diagnosis I ran into the following question: What is the earliest point I can register the (time of onset of the) touch of the screen?
Now I put timestamps in hitTest in all the UIResponders (UIWindow -> UIView -> ... -> DrawingView). But could there be delays before the first call to hitTest in UIWindow?
Thanks!
UIWindow delivers touch events to gesture recognizers before delivering the events directly to views. This happens inside -[UIWindow sendEvent:](https://developer.apple.com/documentation/uikit/uiwindow/1621614-sendevent). I believe it hit-tests the view hierarchy to find the gesture recognizers that might be interested in the event, so you should not expect the hitTest:withEvent: messages to be delayed.
If there is a gesture recognizer on your view or any of its superviews, that gesture recognizer can delay the delivery of touch events.
Note that many of UIKit's standard views use gesture recognizers. In particular, UIScrollView uses gesture recognizers that can delay touch events, and both UITableView and UICollectionView are subclasses of UIScrollView.
Is there a way to register all touch up through touchesEnded. It will only fire when there is a tap that lasts more than a second or two. Is there away to have it fire on all touch ups? It registers all touchesBegan.
Here is the simple code:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touch Ended");
}
-touchesEnded:withEvent: gets called even for brief touches, like taps. You probably have something in your responder chain that's handling the touch. For example, scroll views will normally delay touches until they figure out whether the user is trying to scroll, and gesture recognizers can also delay touch events. Specifically, UIGestureRecognizer has a delaysTouchesEnded property that may be interfering with your code.
Multiple touches are disabled by default. If you want to receive multiple touch events you must set the a multipleTouchEnabled to YES of the view which you use. To do this in your ViewDidLoad just write self.view.multipleTouchEnabled= YES because it's just a property. Another issue may be connected with Magic Trackpad because it adds a delay before it decides that you have ended a touch.Try to disable it : >System Preferences>Personal>Universal Access>Mouse & Trackpad>Trackpad Options>Ignore Trackpad when mouse is present.
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.
I have a UITableView which I present in a UIPopoverController. The table view presents a list of elements that can be dragged and dropped onto the main view.
When the user begins a pan gesture that is principally vertical at the outset, I want the UITableView to scroll as usual. When it's not principally vertical at the outset, I want the application to interpret this as a drag-and-drop action.
My unfortunately lengthy journey down this path has compelled me to create a custom UIGestureRecognizer. In an attempt to get the basics right, I left this custom gesturer as an empty implementation at first, one that merely calls the super version of each of the five custom methods Apple says should be overridden:
(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;
(void)reset;
This results in nothing happening, i.e. the custom gesture's action method is never called, and the table view scrolls as usual.
For my next experiment, I set the gesture's state to UIGestureRecognizerStateBegan in the touchesBegan method.
This caused the gesture's action method to fire, making the gesture appear to behave just like the standard UIPanGestureRecognizer. This obviously suggested I was responsible for managing the gesture's state.
Next up, I set the gesture's state to UIGestureRecognizerStateChanged in the touchesMoved method. Everything still fine.
Now, instead, I tried setting the gesture's state to UIGestureRecognizerStateFailed in the touchesMoved method. I was expecting this to terminate the gesture and restore the flow of events to the table view, but it didn't. All it did was stop firing the gesture's action method.
Lastly, I set the gesture's state to UIGestureRecognizerStateFailed in the touchesBegan method, immediately after I had set it to UIGestureRecognizerStateBegan.
This causes the gesture to fire its action method exactly once, then pass all subsequent events to the table view.
So...sorry for such a long question...but why, if I cause the gesture to fail in the touchesBegan method (after first setting the state to UIGestureRecognizerStateBegan), does it redirect events to the table view, as expected. But if I try the same technique in touchesMoved (the only place I can detect that a move is principally vertical), why doesn't this redirection occur?
Sorry for making this more complicated than it actually was. After much reading and testing, I've finally figured out how to do this.
First, creating the custom UIGestureRecognizer was one of the proper solutions to this issue, but when I made my first test of the empty custom recognizer, I made a rookie mistake: I forgot to call [super touches...:touches withEvent:event] for each of the methods I overrode. This caused nothing to happen, so I set the state of the recognizer to UIGestureRecognizerStateBegan in touchesBegan, which did result in the action method being called once, thus convincing me I had to explicitly manage states, which is only partially true.
In truth, if you create an empty custom recognizer and call the appropriate super method in each method your override, your program will behave as expected. In this case, the action method will get called throughout the dragging motion. If, in touchesMoved, you set the recognizer's state to UIGestureRecognizerStateFailed, the events will bubble up to the super view (in this case a UITableView), also as expected.
The mistake I made and I think others might make is thinking there is a direct correlation between setting the gesture's state and the chronology of the standard methods when you subclass a gesture recognizer (i.e. touchesBegan, touchesMoved, etc.). There isn't - at least, it's not an exact mapping. You're better off to let the base behavior work as is, and only intervene where necessary. So, in my case, once I determined the user's drag was principally vertical, which I could only do in touchesMoved, I set the gesture recognizer's state to UIGestureRecognizerStateFailed in that method. This took the recognizer out of the picture and automatically forwarded a full set of events to the encompassing view.
For the sake of brevity, I've left out a ton of other stuff I learned through this exercise, but would like to point out that, of six or seven books on the subject, Matt Neuburg's Programming IOS 4 provided the best explanation of this subject by far. I hope that referral is allowed on this site. I am in no way affiliated with the author or publisher - just grateful for an excellent explanation!
That probably happens because responders expect to see an entire touch from beginning to end, not just part of one. Often, -touchesBegan:... sets up some state that's then modified in -touchesMoved..., and it really wouldn't make sense for a view to get a -touchesMoved... without having previously received -touchesBegan.... There's even a note in the documentation that says, in part:
All views that process touches,
including your own, expect (or should
expect) to receive a full touch-event
stream. If you prevent a UIKit
responder object from receiving
touches for a certain phase of an
event, the resulting behavior may be
undefined and probably undesirable.