Drag UILabel from point A to point B - iphone

How can I go about dragging a UILabel from point A to point B on my iPhone application?

Basically, you would need to build the functionality yourself. You could do this by listening to touches in a superview that includes the full draggable area. The method hitTest:withEvent: can tell you if the touch down point is in the label to be dragged.
From there, you can override the touchesMoved:withEvent: method to update the position of the UILabel to keep it aligned with the finger. Updating the position of the label will automatically redraw it for you.
Here's a relevant question for iphone drag/drop.
Once you get the dragging working, I would also recommend paying attention to the location within the UILabel where the first touch was made, and handle the repositioning in such a way that the finger is always on that point within the UILabel. An easier-to-code but worse-looking version is to simply reposition, say, the upper-left corner of the label to be where the finger is, but this could make the label appear to jump when it first starts dragging.
It may sound intimidating, but it's really not that bad -- just a few lines of code in total. Unless you have views that might overlap. That's another few lines, depending on how you want to handle it.

// MARK: - Touch Functions
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let location = touches.first?.preciseLocationInView(quoteView) else { return }
if quoteView.pointInside(location, withEvent: event) {
locationInView = location.y
isInButton = true
} else {
isInButton = false
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if isInButton {
guard let totalYMovement = touches.first?.preciseLocationInView(self.view).y,
locationInView = self.locationInView else { return }
var yMovement = totalYMovement - quoteViewLocation
if locationInView >= (self.quoteView.frame.height / 2) {
// Works!
yMovement = yMovement - (locationInView - (self.quoteView.frame.height / 2))
} else {
yMovement = yMovement + ((self.quoteView.frame.height / 2) - locationInView)
}
self.quoteView.transform.ty = yMovement
}
}
I used the above code to translate a view along the y axis. The quoteViewLocation variable is a gettable CGPoint which returns
self.view.frame.height / 2
locationInView is a optional CGPoint that is set every time a tap is made inside of the view to be transformed is tapped.
You can place the button anywhere in your view using my code, just make sure your quoteViewLocation (Whatever you are going to call it) variable returns the CGPoint.y horizontally bisecting the view. This point must be the corresponding point of the superview. What I mean by this is if you have a child view with a height of 40 points and a super view with a height of 40 points and the child view is centered smack dab in the middle of the super view you may think the correct CGPoint to return is 20. Nope! While this is the correct center Y of the child view the center of the super view is 200. Return the CGPoint of the superview.
You have to do this to keep the transformation smooth.
If you want to move a view along the x axis it's the same idea.
If I missed anything or misspoke tear into me. Trying to learn here too.
Thanks

Related

touch transparent area of spritenode

I have a few button with amorphous look. (the Rect of the buttons are intersecting) First I evaluated the BeganTouch in the GameScene. Then I get several touches.
Since I have in my buttons still child nodes, they have swallowed the touchs. Ok, I have made with the help of here a subclass of the SpriteNodes and processed the touch inside the subclass. Now I have the problem that the first button does not pass the touch to the underlying sprites.
But now I would like top ignore the transparent areas of the buttons, how can I do that?
I have read that one can work with physicsbody, I have tried, gravitiy = 0 but since I move and scale the buttons via actions there were violent effects.
Why can I check for the alpha in the touch location and pass the touch to the next sprite.
by the way: how can I get a reference to the view? to get the global loc.
let loc = touch.location(in: view)
with the global touch location I could check all sprites under this point for the alpha!
you can try passing the touch to it's parent (presumably the scene) from your subclass.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
//handle whatever code you want your subclass to do
...
//pass the touch event to the parent
super.touchesBegan(touches, with: event)
}
}
and then in your scene, cycle through the your buttons (in this example I have the buttons in a button array)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: self)
for button in buttons {
if button.contains(touchLocation) {
//handle all other buttons
...
}
}
}
}
although this seems a little redundant to do the touches in two different locations. #Knight0fDragon is correct, it seems odd to have a button have transparent areas.
ok, it's simple, all buttons are from the same subclass. this subclass delegates to an method in the GameScene. here I can check with
allNodes = nodes(at: globalLocation)
now I can check for the name of the node, calculate the point of the pixel inside each node and get the alpha value.
thanxs all

Determine if UITouch is within SKLabelNode frame

My goal is to find out if a UITouch took place within a SpriteKit label node. The view is an SKView. The issue with the code that I am using (below) is that the touch and the rectangle seem to be in different coordinate systems.
I could use simple math to correct this, but is there a simple way to correct this issue? Or is there another way I should be doing this?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let position = t.location(in: view)
//if inside startButton
if (startButton?.frame.contains(position))! {
debugPrint("yes")
}
}
}
I assume that you are are overriding touchesBegan within a scene. If so, then try using let position = t.location(in: self), i.e. replace view with self. That way you get the position within the scene itself, rather than the position within the view that holds the scene.
Hope that helps!

Spritekit move camera only if not dragging from sprite

In a game I'm building a user is able to move the camera around by dragging around the screen similar to a scrollview. This is all handled in the main scene file by the touchesBegan and touchesMoved method. However I also want the ability for a user to drag from an SKSpriteNode (not actually moving it) to another SKSpriteNode - I then perform an action based on the 2 nodes. I can do both of these fine separately. But when a user is dragging from one node to another, I don't want the camera to pan.
Again I've managed to resolve this using a boolean flag that's set on touchesBegan if they have touched a node. However the screen then doesn't pan if a user starts touching on one of the sprites but finishes off of one e.g they actually quickly performed a pan that happened to start on a node. Does anyone have any good solutions for this?
One thought I had was to store all the touch events in touchesMoved, and then loop through them in touchesEnded performing the same movement logic, provided they didn't start and end on a sprite. e.g
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
movingTouches.append(touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if startedOnSprite && !endedOnSprite || !(startedOnSprite && endedOnSprite) {
let location = movingTouches.last().location(in: self)
let previousLocation = movingTouches.first().location(in: self)
let deltaY = location.y - previousLocation.y
let deltax = location.x - previousLocation.x
gameCamera?.position.y -= deltaY
view.frame.size.height
gameCamera?.position.y = (gameCamera?.position.y)! > viewHeight/2 ? viewHeight/2 : (gameCamera?.position.y)!
gameCamera?.position.y = (gameCamera?.position.y)! < -(viewHeight/2) ? -(viewHeight/2) : (gameCamera?.position.y)!
gameCamera?.position.x -= deltax
gameCamera?.position.x = (gameCamera?.position.x)! < -(viewWidth/2) ? -(viewWidth/2) : (gameCamera?.position.x)!
gameCamera?.position.x = (gameCamera?.position.x)! > (viewWidth/2) ? (viewWidth/2) : (gameCamera?.position.x)!
}
movingTouches = [UITouch]()
endedOnSprite = false
startedOnSprite = false
}
To complicate matters I also want the Sprites to have a tap event on them as well as dragging. I'm struggling to find a good way to do all this in SpriteKit
Unfortunately the above isn't very smooth at all, and in think it appears to not even scroll the distance I would expect either (the exact same code does scroll correctly if I'm not bothered about whether or not it starts on a Sprite)
So to be clear, I want scrolling behaviour, provided a user isn't actually using a finger to 'draw' from one sprite to another
Gesture recognisors on your scene would do what you need, keeping taps and swipes separated.

touchesMoved not functioning properly

I am trying to implement an overriden touchesMoved func to enable SKSpriteNodes to be moved around by the user. However, I have to move the node very slowly for it to follow my touches when I drag. In addition, there are three SKSpriteNodes in the background (which you will see I explicitly set to .userInteractionEnabled = false) and these nodes will occasionally respond to the touches. Any help would be greatly appreciated. Let me know if there are any other parts of the code you need.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
var positionInScene = CGPoint(x: 375.0, y: 400.0) //sets a default position
titleLabel.userInteractionEnabled = false
drawingBoard.userInteractionEnabled = false
sideBar.userInteractionEnabled = false
for touch in touches {
positionInScene = touch.locationInNode(self)
if self.nodeAtPoint(positionInScene) is SKSpriteNode {
if (self.nodeAtPoint(positionInScene)).name == movableNodeName { //movableNodeName is the name assigned to all SKSpriteNodes that should be draggable
//I know this might be a strange way of doing it
(self.nodeAtPoint(touch.previousLocationInNode(self))).position = positionInScene
}
}
}
}
I've managed to fix this issue by just setting the coordinates of the node that shouldn't move back to what they were originally whenever it is moved. This seems to work as I haven't been able to replicate the bug again in testing. If anyone could come up with a better solution though, I would love to hear it.
Your problem is you are using touch.locationInNode(self), where self is your SKSpriteNode. This means that your touch move code will only respond to what is going on inside of your SKSpriteNode. What you need to do, is use either the parent of the sprite, or the scene, based on how you want to apply this logic.

How to make touch.locationInNode() recognize the difference between a node and its child?

I started out by declaring two SKSpriteNodes, handle and blade, and adding handle as a child of self, and blade as a child of handle
var handle = SKSpriteNode(imageNamed: "Handle.png")
var blade = SKSpriteNode(imageNamed: "Blade.png")
override func didMoveToView(view: SKView) {
handle.position = CGPointMake(self.size.width / 2, self.size.height / 14)
blade.position = CGPointMake(0, 124)
self.addChild(Handle)
Handle.addChild(Blade)
}
When I click on the handle, it prints to the console "Handle was clicked", however when I click on the Blade, it also prints "Handle was clicked". It is clearly recognizing that the blade is a child of handle, but how can I make it so when I click on blade, it prints "Blade was clicked"?
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if (Handle.containsPoint(location)){
NSLog("Handle was clicked")
}
else if (Blade.containsPoint(location)){
NSLog("Blade was clicked")
}
}
}
Determining whether the user touched the sword's handle or the blade is fairly straightforward with some caveats. The following assumes that 1. the sword image is facing to the right when zRotation = 0, 2. the anchorPoint of the sword is (0, 0.5), and 3. the sword (blade and handle) is a single sprite node. When you add a sprite to another sprite, the size of the parent's frame expands to include the child node. That's why your test of Handle.containsPoint is true no matter where you click on the sword.
The figure below shows a sword sprite with a dark gray handle (on the left) and lighter gray blade. The black rectangle surrounding the sword represents the sprite's frame and the circle represents the location of the user's touch. The length of the line labeled a is the distance from the touch point to the bottom of the sword. We can test this distance to see if the user touched the handle (if a <= handleLength) or the blade (if a > handleLength). When zRotation = 0, a = x so the test is x <= handleLength, where the bottom of the sword is x = 0.
In the below figure, the sword is rotated by 90 degree (i.e., zRotation = M_PI_2). Similarly, if a <= handleLength, the user touched the handle, else the user touched the blade. The only difference is a is now the y value instead of x due to the sword's rotation. In both cases, the frame's bounding box can be used, as is, to detect if the user touched the sword.
When the sprite is rotated by 45 degree, however, its frame automatically expands to enclose the sprite as shown by the black rectangle in the figure below. Consequently, when the user touches anywhere in the rectangle, the test if sprite.frame.contains(location) will be true. This may result in the user picking up the sword when the location of the touch is relatively far from the sword (i.e., when the distance b is large). If we want the maximum touch distance to be the same across all rotation angles, additional testing is required.
The good news is Sprite Kit provides a way to convert from one coordinate system to another. In this case, we need to convert from scene coordinates to the sword coordinates. This greatly simplifies the problem because it also rotates the point to the new coordinate system. After converting from scene to sword coordinates, the converted touch location's x and y values are the same as the distances a and b over all rotation angles! Now that we know a and b, we can determine how close the touch was to the sword and whether the user touched the handle or the blade.
From the above, we can implement the following code:
let location = touch.locationInNode(self)
// Check if the user touched inside of the sword's frame (see Figure 1-3)
if (sword.frame.contains(location)) {
// Convert the touch location from scene to sword coordinates
let point = sword.convertPoint(location, fromNode: self)
// Check if the user touched any part of the sword. Note that a = point.x and b = point.y
if (fabs(point.y) < sword.size.height/2 + touchTolerance) {
// Check if the user touched the handle
if (point.x <= handleLength) {
println("touched handle")
}
else {
println("touched blade")
}
}
}
This should work without changing too much of your existing code...
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let locationInScene = touch.locationInNode(self)
let locationInHandel = touch.locationInNode(Handle)
if (Blade.containsPoint(locationInHandel)){
NSLog("Blade was clicked")
}
else if (Handle.containsPoint(locationInScene)){
NSLog("Handle was clicked")
}
}
}
Note you are checking for blade first then you check for handle. Also note you have to convert the touchpoint to give you a point from within handle.
With that being said this will work on a small scale, but you may want to look at creating a subclass for SKSpriteNode called Handle or Sword (this is why you don't use first caps for variable names they are normally only use first caps for classes), set it to userInteractionEnabled and then override touchesBegan in that subclass and see if it is touching the blade and if not you know it touched the handle.
Hopefully that helped and made sense.