tvOS - strange touchesCancelled received - sprite-kit

I am having a user input issue in my tvOS SpriteKit project - if I try to move the finger quickly after touching the touchpad on the tv remote I do have touchesCancelled called while if I wait a little while (1s) after first touch (wihout releasing my finger) all works like charm and I can use this tiny interface for steering in my game.
Unfortunately there is no specific code I can paste.
this guy:
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) { ... }
is being called by the system in case a quick finger move but not called if I wait for 1-2 seconds before I start moving my finger.
Any idea what I could have done wrong?

ah .... UISwipeGestureRecognizer from previous Scene was hanging around :(
func removeAllGestureRecognizers(){
for recognizer in self.view!.gestureRecognizers!{
self.view!.removeGestureRecognizer(recognizer);
}
}
fixed my issue ...

Related

Stop propagation of touch event to views behind in Swift

I have a scrollview and a subview inside it. If the user does a certain touch in that subview, I want to prevent the scrollview to move, in order to let the user do their stuff in the subview without being bothered by the scrollview movement. If the certain touch is not detected, then the scrollview should move normally .
I intercept these gestures with raw touch events touchesBegan, touchesMoved. I don't use gestureRecognizers because the gestures I want to recognise are very specifics and I feel more confortable using no abstraction layer to recognise them.
I know after seeing many answers on SO, I could just hold a reference of the scrollview behind and stop its movement if I detect the gesture. I'm looking for a more stable solution. I want to stop the propagation of the event (to any view behind), if I detect the gesture, without having to hold the references of any of these views behind.
As I understood, view in iOS are subclasses of UIResponder. When UIKit detects a touch on the screen, it gives the event to the first responder, which is generally the top most subview. My question is : in touchesBegan how to tell UIKit : "Do not send the event to any other following view in the responder chain". If I can see the scrollview moving behind, UIKit must have forwarded the event to it (despite I'm not calling super in touchesBegan)
In Android for example, onTouchEvent function of View class returns a bool. false tells android to continue propagating the event, true tells to stop propagating. I'm looking for the same mechanism in iOS:
#Override
public boolean onTouchEvent(MotionEvent e)
{
return true ; // stops propagation
}
In Javascript (jQuery), there's quite the same mechanism :
$('#myview').bind('click', function(e) {
e.stopPropagation()
})
How to do that in Swift ?
Found the solution. This was tricky to understand so I'll try to make it easy if anyone has the same problem :
As stated in another SO answer, the "raw touch system" ( touchesBegen, touchesMoved ..) and the "gesture recognizer system" are mutually exclusive, both of them are actually at the "raw" view level, and there are independent.
This means that when you have a view and you touch it, you have a chance that your touch is handled by the gestureRecognizer system instead of the raw touch system. overriding next UIResponder property by override var next:UIResponder? { get { return nil }} only force UIKit not to forward the event in the raw touch system, the gesture recognition of views behind are still fired, because it's a system completely apart.
In my case, I tried to override var next:UIResponder? { get { return nil }} : the touchesBegan of the views behind remained quiet as expected, but I could still recognize gesture there.
So, it appears that UIScrollView uses gestureRecognizer to handle user touches. The solution is to shut down the gesture recognizer system from your top most view so the gesture is not forwarded : this can be done using :
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
{
return !yourConditionToShutDownGR
}
I was confused because the Android gesture "detector" system is built on top of the raw touch system. When you catch a touch event in public boolean onTouchEvent(MotionEvent event) you can pass it as a parameter of a gestureDetector, which returns if it found a gesture or not. That's not the same approach for iOS in which the gestureRecognizer system is built aside of the raw touch system.

Change UIButton color for duration of AVAudioPlayer sound play

I am trying to change the color of a UIButton when pressed. The button triggers AVAudioPlayer and I am trying to get the button color to remain changed until the sound is done playing and then revert back to original color. The code I have changes the button color once pressed and remains at the changed color after the sound is done, but doesn't change back when the player is not playing.
#IBAction func soundPlay(_ sender: UIButton) {
soundPlayer?.play()
if (soundPlayer!.isPlaying == true) {
playButton.backgroundColor = orangeColorLight
}
else {
playButton.backgroundColor = orangeColor
}
}
Per a recommendation in the comments, I've attempted to implement an audioPlayerDidFinishPlaying delegate without success. There is little documentation on how to properly implement this unfortunately so it was pretty much guess work.
#IBAction func soundPlay(_ sender: UIButton) {
playButton.backgroundColor = orangeColorLight
soundPlayer?.play()
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
successfully flag: Bool){
playButton.backgroundColor = orangeColor
}
}
My logic above was to set the lighter color upon button press > play sound > use audioPlayerDidFinishPlaying to watch for sound to finish playing > change color back.
When you say play, you know the sound player is playing, so there is no point following up with a condition; just change the play button color.
Your code is now over. It isn't just going to sit there watching while we wait for the sound to end. There is no such thing as waiting in iOS code. (Well, there sort of is, in extreme cases, but never mind that now.) What you need is to be called back by the sound player when the sound ends.
Well, you can do that! Set yourself as the player's delegate and implement this delegate method:
https://developer.apple.com/documentation/avfoundation/avaudioplayerdelegate/1389160-audioplayerdidfinishplaying
It will be called when the sound ends, and now you can set the color back again in response.
What I have just shown you is the key concept you need to understand in iOS programming. Your code is event-driven. It doesn't stop and wait for things; instead, you need to arrange to hear whenever something happens that interests you. Cocoa provides events to tell you about all sorts of things; your job is put the right code in the right place with the right name so that your code is called when something of interest happens.

How to suspend touchesBegan and only use touchesMoved vice versa?

At the moment I am programming a game in swift, Xcode and I would like the user to be able to choose what controls they want (either slide or tap). As of now I have been able to make my SKNode slide in the main game scene, as well as respond to tapping. However I have found that the SKNode shakes constantly when you slide (as the touchesBegan function thinks I'm tapping on the screen).
If anyone knows the best way to suspend one of the functions while the other stays active please let me know :)
For example:
The user chooses to play the game by sliding, I would like to suspend the touchesBegan function but keep the touchesMoved active.
There is a delegate method to check if one gesture fails, if is true the other gesture is recognized. For example, in my project I have two gestures, tap once and tap twice and I used:
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if otherGestureRecognizer.numberOfTouches == 2 {
return true
}
return false
}
For more information check Apple's reference:
https://developer.apple.com/documentation/uikit/uigesturerecognizer/1620006-shouldrequirefailure

Fidget Spinner Animation Swift?

I've got a request to make a fidget spinner animation inside one shopping app, its meant to give the user a 'feel' before clicking buy so its a UIImageView that needs to be animated. I've added a custom single touch gesture recognizer that allows the user to spin/rotate the view, however the image now only rotates and stops as soon as the user lets go, what would be the best way to keep the 'velocity' going? Think of how something would be done with SpriteKit?
I left a comment regarding angular velocity and momentum but wanted to answer this question so the next person who needs help knows a way to do this.
Angular velocity is the speed of the spinning. What you want to do is apply an angular impulse which will start the spinning. The mass and angular momentum will slow it down as time passes, just like the actual physics behind the fidget spinners.
Add this to the touchesBegan method in the fidgetSpinner class
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
fidgetSpinner.physicsBody?.applyAngularImpulse(1)
}
Or add it to the scene but check that the touch was inside and on fidgetSpinner.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first?.location(in: self)
if fidgetSpinner?.contains(touch) {
fidgetSpinner.physicsBody?.applyAngularImpulse(5)
}
}

How to catching doubleClick events from NSOutlineView in ViewController?

I am trying to catch doubleClick events from my NSOutlineView to be passed to my ViewController. My idea is to catch doubleClick events and to get the selected row from my OutlineView
What I did so far was subclassing the NSOutlineView in order to overwrite mouseDown
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
if event.clickCount >= 2 {
...
}
}
That works well however I don't know how to pass this event to my ViewController. The ViewController is already implementing the NSOutlineViewDelegate protocol.
I guess that the solution is not far away but somehow I am stuck.
UPDATED
Although you can set up NSGestureRecognizer for single click and NSClickGestureRecognizer for double clicks in OSX, You should probably be using the doubleAction property of the NSOutlineView directly.
Here's an example of how to set it up
This comes from a another of the Wenderlich tutorials, and there is a good discussion on SO already