I'm implementing shake gestures as described in this answer however if I shake my phone for longer than a second, motionEnded doesn't get called.
Is there not a 1-to-1 ratio of motionBegan and motionEnded events guaranteed by the OS,
or is this a problem with the responder chain and events are getting sent elsewhere?
Either motionEnded:withEvent: or motionCancelled:withEvent: should be called. From the docs for motionCancelled:withEvent:
This method is invoked when the Cocoa Touch framework receives an interruption requiring cancellation of the motion event. This interruption is something that might cause the application to be no longer active or the view to be removed from the window. The method can also be invoked if the shaking goes on too long.
Related
I am trying to write an App for Apple tv4 (tvos). When my App starts, the view controller does receive touchesBegan events, as it should.
Without going into too many details, the App creates, moves, and deletes sub-views to respond to the user's interactions.
After a while, the view controller does not receive touchesBegan any more (this is the strange error that I am trying to debug).
Since I think the problem has something to do with the responder chain, I have made the following two experiments:
If I let the view controller override and return true from canBecomeFirstResponder, then the problem still occurs, but it occurs much less frequently.
If I do not override that function and instead check who is the first respnder, then I find that the App has no first responder, even before the strange error occurs. That is to say, the App has no first responder even when it is working properly!
Questions: What can prevent touchesBegan from being invoked? Is it related to the responder change? If so, please explain 2 above.
How exactly are you supposed to "touch" a view rendered on a non-touch screen enabled TV?
You're not.. tvOS doesn't work like iOS in the way that you cannot detect touches because there is no touch screen enabled input device supported on an Apple TV.
Instead, you use the UIFocusEngine to handle interactions with content presented within your view hierarchy.
Check out "Controlling the User Interface with the Apple TV Remote" from Apple's Developer Library for more information.
I have seen several posts (E.g. this one) on this but I still don't get it.
If I call the CCDirector pause method it sets the animation interval to 1/4 and the value isPaused_ to YES (see code below). In the CCDirector.m class the isPaused_ variable
doesn't seem to be used much apart of in the pause and resume methods.
I thus decided to call also the stopAnimation method but in some posts this is not mentioned:
[[CCDirector sharedDirector] stopAnimation]
It doesn't apparently stop accelerometer data and input data to be sent to the main scene. It kind of does make sense to me because the developer of the Game might want to allow the user to resume by shaking the iPhone or tapping a resume button. Is this the reason behind this choice?
Also, why is the animation interval set to 1/4 and why things even with this value don't move (apart of my player entity that moves using accelerometer input)?
Thanks a lot!
-(void) pause
{
if( isPaused_ )
return;
oldAnimationInterval_ = animationInterval_;
// when paused, don't consume CPU
[self setAnimationInterval:1/4.0];
[self willChangeValueForKey:#"isPaused"];
isPaused_ = YES;
[self didChangeValueForKey:#"isPaused"];
}
Pausing CCDirector reduces framerate to 4 fps in order to conserve both battery and CPU cycles. The latter is necessary if you use UIKit views, in some cases it is necessary to pause the director in order for UIKit animations to animate smoothly.
What pausing the director also does is that it stops updating all nodes. Specifically the CCScheduler doesn't fire any scheduled selectors or update methods. It also prevents touches from being passed on to nodes, because they are relayed through the CCTouchDispatcher which is also paused when the director is paused.
But as you noticed, the accelerometer isn't paused. That's because cocos2d doesn't provide a wrapper for UIAccelerometer and therefore you get these notifications directly from iOS, ignoring the pause status of the director. If you then change the position of nodes inside the didAccelerate method or another method called directly from it, that node will change its position despite director being paused.
This is but one reason why director's pause isn't really suitable for a "pause game" feature. Another issue is that pausing simply would prevent any pause menu built with cocos2d features would also be paused, which kind of defies the purpose.
Then startAnimation and stopAnimation are simply extreme measures that prevent cocos2d from updating the screen altogether. This is normally only used in situations where either the cocos2d view is removed temporarily from the view hierarchy, or hidden, or some UIKit view is going fullscreen. In that sense stopAnimation is like a suspend feature.
I am able to understand that when user just touches the view, touches Began and Ended called. When user swipes their hand on a view, touches Moved method gets called. But when does touches Cancelled get called or by what action on user this method gets called?
I think probably the most common reason for touchesCancelled being called (since iOS 3.2 anyway) is following the recognition of a gesture by a UIGestureRecognizer. If your view has any kind of gesture recognizer attached to it then it is often very important to provide a custom implementation of the touchesCancelled method - note this includes ready made views that use gesture recognizers, including UIScrollView.
By default, gesture recognizers cancel the delivery of touches to the hit-test view upon recognition, although this behaviour can be disabled. This involves sending the touchesCancelled message to that view, most likely following a touchesBegan or touchesMoved message. If your touch handling code is relying on code implemented in the touchesEnded method, it is possible this may never be fired and some kind of serious problem could occur, hence the need to properly tie up any loose ends in touchesCancelled.
The ins and outs of gesture recognizer functionality is obviously a bit more complex than I've mentioned here - I would thoroughly recommend reading Apple's Gesture Recognizers documentation.
Also, check out the WWDC videos on gesture recognizers (starting from 2010).
Note: touches also get cancelled if you start a UIView animation after touchesBegan. To prevent this make sure you include UIViewAnimationOptionAllowUserInteraction:
e.g.
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
self.aView.hidden = NO;
self.aView.alpha = 1;
} completion:nil];
From the Apple Reference documents
Sent to the receiver when a system
event (such as a low-memory warning)
cancels a touch event.
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.
And, from the Event Handling Guide for iOS, p. 19:
It sends the touchesCancelled:withEvent: message when the touch sequence is cancelled by a system event, such as an incoming phone call.
I was handling touchesBegan()/touchesMoved() on a view under UIScrollView, which is challenging. My touches kept cancelled by somewhere when I pinch (somehow it is OK with single touch movement), I was investigating how to stop being cancelled. I figured out, that there is a property Can Cancel On Scroll on UIScrollView, and you may check it off to stop being cancelled, if your case is similar to my case.
It sounds there are many cases where your touches are being cancelled, so my answer is just one of them.
My app is playing a pretty complex animation. It's like a flipbook.
What I do is: I have a huge loop with selectors, and after every delayed call the next one is called.
Now someone calls the user and the device suddenly shows up this fat green status bar and maybe some big pick-up-the-phone-call overlay. Or: The alarm clock rings, and a big alert sheet appears in front of just about everything.
It would be great to just pause the whole animation in case of ANY interruption. Probably I've also missed like 5 more possible interruptions.
How are you doing that? How do you get notified for all interruptions and then call one single -stopEverything method?
Whenever the app becomes inactive, the UIApplicationWillResignActiveNotification local notification will be posted. In the opposite suituation, the UIApplicationDidBecomeActiveNotification notification will be posted.
Your animation logic can listen to this and respond appropriately. There is an +setAnimationsEnabled: method to kill all current and future animations, but there isn't a documented "global pause" method.
Depending on the animation, you might be better off using CoreAnimation directly by using a single CAKeyframeAnimation on the view's -layer rather than having one animation's completion selector start another animation.
The function touchesMoved behaves differently in iPhone and simulator.
The repeating interval (refresh rate) of the function touchesMoved is much faster than simulator. Is there a way to deal with the difference?
Often people are finding this to be a problem because they are doing something intensive in the touchesMoved handler and when events arrive very frequently, it makes the interface appear to lag.
A relatively simple way to deal with this is: First of all, in the touchesMoved handler, store the touch position in a variable that represents the position of whatever is tracking the finger.
Return from the touchesMoved handler immediately. Create an NSTimer object and set your view controller as a delegate of it and have that do whatever re-drawing/view moving behaviour used to be in your touchesMoved handler. Thus, you get a near constant movement regardless of the time between touchesMoved events.
If you're really advanced you can use a custom NSRunLoop instead of using a timer, but that's more than I can explain here :) The general idea is: don't be doing everything in the touch event handlers.