How to suspend touchesBegan and only use touchesMoved vice versa? - swift

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

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 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

SWRevealViewController conflict with UISlider

I currently facing an issue trying to delegate SWRevealViewController panGestureRecognizer method in one of my view.
When i slide my UISlider, the panGesture interfer and open the sidemenu instead to move my slider.
i tried to delegate the panGesture and it works well, but if i quit my view and go to an other, the pangesture is not functionnal anymore, and i can't reveal my sidemenu from my second view.
My code :
class Search : UIViewController, UIGestureRecognizerDelegate{
#IBOutlet weak var sliderprice: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
self.revealViewController().panGestureRecognizer().delegate = self
}
override func viewDidAppear(animated: Bool) {
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if (touch.view == self.sliderprice){
return false
}
else{
return true
}
}
}
While this is an old question, I'll answer it anyways, maybe for someone coming from Google search it will be helpful.
After many hours of research and thinking, finally I was able to come up with multiple solutions to this problem.
The Lazy
This solution is not recommended, but working, so I decided to list it.
Before setting self as self.revealViewController().panGestureRecognizer()'s delegate, store the original self.revealViewController().panGestureRecognizer().delegate to a property, and when you leave the screen at viewWillDisappear(), set self.revealViewController().panGestureRecognizer().delegate back to the one you stored in viewDidLoad(). So in the end it gets back its original delegate. Tampering with delegates like this is never really recommended, but I said, it works.
The Nicer One
I consider this still not the best solution, but we are getting there. Find a class, a controller that you use in the whole application and gets called when you start the app. Here set the SWRevealViewController's panGestureDelegate to this class, and overwrite the gestureRecognizerShouldBeginmethod appropriately (see below).
The Best One - (in my opinion)
Now this is the best and most clear solution.
For the time being (May, 2018) the last commit to SWRevealViewController was in 2015.
Fork the original project ( https://github.com/John-Lluch/SWRevealViewController ) or simply copy the two necessary files (SWRevealViewController.m and SWRevealViewController.h) and place them into a separate folder to handle 3rd party libraries. Then you can remove SWRevealViewController from your Podfile. Don't forget to fix your imports for SWRevealViewController where you have it.
Now you are free to modify the files. What I suggest is the following.
Go to SWRevealViewController.m and implement the following method:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
return ![touch.view isKindOfClass:UISlider.class];
}
If the touched view is a UISlider (or a custom class that inherits from UISlider) the gesture won't begin, meaning the pan gesture will no longer be conflicted with the UISlider's pan gesture.
I seriously hope I could help anyone out there as this problem was a pain in my back for quite some time now.

Swift: how to disable user interaction while touch action is being carried out?

I'm working with sprite kit and if the user touches the screen, the actions within
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
}
}
are carried out. While they're being carried out, however, the user can still tap the screen and the app tries to run the actions again.
How do I disable touch interaction/the actions within the touch func while the actions are running?
To disable user interaction app-wide, use:
UIApplication.shared.beginIgnoringInteractionEvents()
UIApplication.shared.endIgnoringInteractionEvents()
(as of Swift 5 this is deprecated)
Try to get the view from the touch object and then dissable the user interaction on it.
touch.view.isUserInteractionEnabled = false
In Swift 3.0 is:
self.view.isUserInteractionEnabled = false
You can use boolean class variable to stop interaction while method is performing, and after that you can just change value of boolean, at the end of the method.
Use UIApplication.shared.beginIgnoringInteractionEvents() at the end of the first method, then change value of boolean and then use another method with start line UIApplication.shared.endIgnoringInteractionEvents().
- SwiftUI
SomeView()
.allowsHitTesting(false)
For SwiftUI, expanding on #mojtaba-hosseini answer.
You wanna make sure you fill your view with some color, except Clear color (you can always change opacity). I've also added blur to hide elements. Here's what worked for me:
SwiftUI:
ZStack{
SomeView().blur(radius: 12)
Rectangle()
.fill(Color.white.opacity(0))
.allowsHitTesting(false)
}
Another way to disable user interactions like scroll or button taps, but attach an action to user taps (for example a message to users that this feature is coming or behind a paywall):
SwiftUI:
VStack{
SomeView().blur(radius: 12)
}
.contentShape(Rectangle())
.onTapGesture {
print("No access!")
}
just go to storyboard and uncheck this option in any object which u want to disable the interaction of the user
If you want to disable a Buttons user's interaction, just do this when the screen loads.
self.btnname.isUserInteractionEnabled = false
If you want to disable the user interaction as a view, it will be same just remove the button name and add the view's name.
If for some reasons, you want to enable interaction, just change "false" to "true".