Basically I need to create a UIScrollView in my SpriteKit project but i'm having a lot of problem adding SKButtons (custom SKNode class for button management). So I proceeded to create a scrollable SKNode with touch gesture but, obviously, this bar won't have the native UIScrollView acceleration: the feature I was looking for.
So tried to overtake this problem by adding a native UIScrollView and catch every change of position, like this:
using this code:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[blueNode setPosition:CGPointMake(blueNode.position.x, scrollerUI.contentOffset)];
}
This works right but foolishly I forgot that if I add buttons, the touch gesture won't recognize the button's touch event! (The UIScrollView has the priority).
Maybe is just a stupid question but I don't really know how to figure it out. Maybe programming my own acceleration methods?
I think I came up with a solution to handle touches. Since the UIScrollView eats all the touches to allow it to scroll you need to pass them to the SKScene. You can do that by subclassing UIScrollView:
class SpriteScrollView: UIScrollView {
var scene: SKScene?
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
scene?.touchesBegan(touches, withEvent: event)
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
scene?.touchesMoved(touches, withEvent: event)
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
scene?.touchesCancelled(touches, withEvent: event)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
scene?.touchesEnded(touches, withEvent: event)
}
}
Then your scene needs to see if the touch hits any nodes and appropriately send the touches to those nodes. So in your SKScene add this:
var nodesTouched: [AnyObject] = []
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
let location = (touches.first as! UITouch).locationInNode(self)
nodesTouched = nodesAtPoint(location)
for node in nodesTouched as! [SKNode] {
node.touchesBegan(touches, withEvent: event)
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
for node in nodesTouched as! [SKNode] {
node.touchesMoved(touches, withEvent: event)
}
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
for node in nodesTouched as! [SKNode] {
node.touchesCancelled(touches, withEvent: event)
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
for node in nodesTouched as! [SKNode] {
node.touchesEnded(touches, withEvent: event)
}
}
Note this is for single touches and does not handle errors well, you'll need to wrap some of the forced down casting in if statements if you want to do this properly
I hope this helps someone even though the question is months old.
One possible solution would be to add tap gesture to scene:
override func didMoveToView(view: SKView) {
let tap : UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tapped:"))
view.addGestureRecognizer(tap)
}
func tapped(tap : UITapGestureRecognizer) {
let viewLocation = tap.locationInView(tap.view)
let location = convertPointFromView(viewLocation)
let node = nodeAtPoint(location)
if node.name == "lvlButton" {
var button = node as! SKButton
button.touchBegan(location)
}
}
Related
I'm implementing a passThroughView by creating a transparent View on top and override hitTest().
passThroughView should consume touches from Apple Pencil and if touch type is not from pencil, it pass touches to the view underneath.
The problems are:
parameter "event" in hitTest contains no touch, so I can't check touch type in hitTest
I can get touch from touchesBegan and check touch type, but it get called only after hitTest returned true
I subclass UIWindow and override sendEvent() but this function also called after hitTest (and I don't know why)
class WindowAbleToKnowTouchTypes: UIWindow {
override func sendEvent(_ event: UIEvent) {
if event.type == .touches {
// This get called after Hittest
if event.allTouches!.first!.type == .pencil {
print("This touch is from Apple Pencil")
}
}
super.sendEvent(event)
}
}
Is there anyway to check touchType to decide to pass or consume touches?
I ended up using a different approach, it could be useful for many cases: If I can't get touchType in hitTest(), I can still get the touchType with GestureRecognize:
class CustomGestureRecognizer : ImmediatePanGesture {
var beganTouch : UITouch!
var movedTouch : UITouch!
var endedTouch : UITouch!
override func shouldReceive(_ event: UIEvent) -> Bool {
// You can check for touchType here and decide if this gesture should receice the touch or not
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
// Save touch for later use
if let firstTouch = touches.first {
beganTouch = firstTouch
}
super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
// Save touch for later use
if let touch = touches.first {
movedTouch = touch
}
super.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
// Save touch for later use
if let touch = touches.first {
endedTouch = touch
}
super.touchesEnded(touches, with: event)
}
}
In the target function of the gestureRecognizer, you can get the UITouch by:
let beganTouch = customGesture.beganTouch
let touchType = beganTouch.touchType
SKNode A is a parent of SKNode B.
If touches begin inside of SKNode B, the touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) function gets called.
If, however, the touches begin outside of SKNode B but move into SKNode B, the touchesMoved function never gets called.
One option is to handle all touch events inside SKNode A.
However, there are many nodes contained inside SKNode A. Ideally, we could push touch functionality into each child, as opposed to handling everything from SKNode A.
Is it possible to capture touch events that occur inside SKNode B even if they began outside of SKNode B?
When you hit your touchesBegan method, your touch is limited to the coordinate system of that node until the point it is ended. Moving onto another node will not change the coordinate system to the new node. If B is the only node that requires the touch, then you can add a child to cover the entire scene to ensure that B gets fired.
class TouchNode : SKSpriteNode
{
//if you need to override other inits do it here
convenience init(withSize size:CGSize)
{
self.init(color:UIColor(red:0,green:0,blue:0,alpha:0.1),size: size)
isUserInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouches>, withEvent event: UIEvent?) {
parent!.touchesBegan(touches:touches, withEvent:event)
}
override func touchesMoved(touches: Set<UITouches>, withEvent event: UIEvent?) {
parent!.touchesMoved(touches:touches, withEvent:event)
}
override func touchesEnded(touches: Set<UITouches>, withEvent event: UIEvent?) {
parent!.touchesEnded(touches:touches, withEvent:event)
}
override func touchesCancelled(touches: Set<UITouches>, withEvent event: UIEvent?) {
parent!.touchesCancelled(touches:touches, withEvent:event)
}
}
Then in your NodeB class:
required init(coder aDecoder:NSCoder)
{
super.init(coder:aDecoder)
DispatchQueue.main.async{
self.addChild(TouchNode(withSize:self.scene!.size))
}
}
override func touchesBegan(touches: Set<UITouches>, withEvent event: UIEvent?) {
//do stuff
}
override func touchesMoved(touches: Set<UITouches>, withEvent event: UIEvent?) {
//do stuff
}
override func touchesEnded(touches: Set<UITouches>, withEvent event: UIEvent?) {
//do stuff
}
override func touchesCancelled(touches: Set<UITouches>, withEvent event: UIEvent?) {
//do stuff
}
Edit: Misread question, but leaving the answer in case somebody else needs to go from inside the node to outside.
Think about it, you want to touch nodeB outside of nodeB, this does not make sense. It is like me poking the air and you crying that I am poking you. But if you absolutely need to go outside of the bounds, then add a child node to the sprite when you touch it that extends beyond the node itself, and be sure to transfer the touch back up to the parent
class TouchNode : SKSpriteNode
{
//if you need to override other inits do it here
convenience init(withSize size:CGSize)
{
self.init(color:UIColor(red:0,green:0,blue:0,alpha:0.1),size: size)
isUserInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouches>, withEvent event: UIEvent?) {
parent!.touchesBegan(touches:touches, withEvent:event)
}
override func touchesMoved(touches: Set<UITouches>, withEvent event: UIEvent?) {
parent!.touchesMoved(touches:touches, withEvent:event)
}
override func touchesEnded(touches: Set<UITouches>, withEvent event: UIEvent?) {
parent!.touchesEnded(touches:touches, withEvent:event)
}
override func touchesCancelled(touches: Set<UITouches>, withEvent event: UIEvent?) {
parent!.touchesCancelled(touches:touches, withEvent:event)
}
}
Then in your NodeB class:
lazy var touchNode = TouchNode(withSize:self.scene!.size)
override func touchesBegan(touches: Set<UITouches>, withEvent event: UIEvent?) {
addChild(touchNode)
//do other stuff
}
override func touchesEnded(touches: Set<UITouches>, withEvent event: UIEvent?) {
touchNode.removeFromParent()
//do other stuff
}
override func touchesCancelled(touches: Set<UITouches>, withEvent event: UIEvent?) {
touchNode.removeFromParent()
//do other stuff
}
Ive just started learning programming in general and ran into a problem:
I have read some articles about that the iPhones are only capable of tracking 5 touches or so... But I realised that when I touch the screen with e.g. 7 fingers at once, my program-let stops working.
So, anyone knows, which parts of the code malfunctions when I am touching the screen way too many times at once?
(I am a newbie.)
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first!
touchLocation = touch.locationInNode(self)
nrTouches += touches.count
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
nrTouches -= touches.count
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first!
touchLocation = touch.locationInNode(self)
}
override func update(currentTime: CFTimeInterval) {
if nrTouches > 0 {
touchingLabel.text = "touching"
} else {
touchingLabel.text = "not touching"
}
}
So, in the case I am touching with 7 fingers at once, "touching" will be displayed all the time.
tankyuu
You're missing touchesCancelled, which gets called when the 6th finger is put down:
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
if let touches = touches {
nrTouches -= touches.count // same logic as touchesEnded
}
}
An interesting note: The iPad can handle 11 touch events. More on that here.
I have strange issue with touchesBegan handler being called more than necessary.
If I tapped the UIView (UIButton) fast 2 times, touchesBegan was called 3 times.
I solved the issue with simple time measurement but I am still interested what would be the reason for this kind of behaviour?
Here is the code (with already added time-checking):
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
if let t:UITouch = touches.anyObject() as? UITouch
{
if !CGRectContainsPoint(CGRectMake(0, 0, self.frame.width, self.frame.height), t.locationInView(self))
{
touchesCancelled(touches, withEvent: event)
}
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
ForCancelTouch = false
setupButtonGUI(true)
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
if !ForCancelTouch
{
if abs(LastValidTouchesBeganDate.timeIntervalSinceNow) > DelayBetweenFastTapping
{
NSNotificationCenter.defaultCenter().postNotificationName(SBDCNotificationNameActionSTBMakeOneCommand, object: self, userInfo: ["tag":self.tag])
LastValidTouchesBeganDate = NSDate()
}
}
setupButtonGUI(false)
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
ForCancelTouch = true
setupButtonGUI(false)
}
Well - it seems the issue happens ONLY when clicking/testing with mouse inside iOS simulator! It looks like it is an iOS simulator bug.
I am overriding:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent)
in Swift.
I am wondering, is this correct for getting one touch point:
var touchPoint: CGPoint! = event.touchesForView(self)?.anyObject()?.locationInView(self)
Thanks in advance!
As of iOS 8.3, touchesBegan takes a touches parameter which is a Set<NSObject>. It's now Set<UITouch>. So, you would:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: view) else { return }
// use `point` here
}
In previous iOS versions, touchesBegan was defined as a NSSet parameter, as outlined in the question, so you would:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
if let touch = touches.anyObject() as? UITouch {
let touchPoint = touch.locationInView(view)
...
}
}