Ok, so I have always wondered how do I actually pick up an image view and drag it. I was planning when I drag an image view and when user places it to correct location it locks there. I really don't have idea how to do this and it has bothered me for sometime.
Thanks so much in advance!
Or you can use a UIPanGestureRecognizer if you don't want to subclass the view and handle touches yourself.
Create a pan recognizer in any class (e.g. view controller) and add it to your view:
UIPanGestureRecognizer * panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:#selector(handlePanGesture:)];
[myDraggedView addGestureRecognizer:panRecognizer];
And then simply:
- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
CGRect frame = myDraggedView.frame;
frame.origin = [gestureRecognizer locationInView:myDraggedView.superview];
myDraggedView.frame = frame;
}
If you like Interface Builder as much as me you can also just drag a pan gesture recognizer over your image view, and then connect the recognizer to the handlePanGesture: that should be declared as an IBAction instead of void.
If you're one for reading, then check out the UIResponder Class Reference, and, in particular, touchesBegan and touchesMoved.
I already worked with this, but the user can drag imageview freely with position that they want. and this is my custom class with swift
class DraggableImageView: UIImageView {
var beganPoint: CGPoint? = nil
var originCenter: CGPoint? = nil
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let position = touches.first?.location(in: superview){
beganPoint = position
originCenter = center
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let position = touches.first?.location(in: superview){
let newPosition = CGPoint(x: position.x - (beganPoint?.x)!, y: position.y - (beganPoint?.y)!)
center = CGPoint(x: (originCenter?.x)! + newPosition.x, y: (originCenter?.y)! + newPosition.y)
}
}
}
Just user this custom class with your UIImageView
Related
At the moment I have a game in which the sprite can be controlled and moved along the x axis (with a fixed y position) using the accelerometer of the device. I wish to change this so the user can control the sprite by dragging on the screen like in popular games such as snake vs.block.
I've already tried using the touches moved method which gives the correct effect, although the sprite first moves to the location of the users touch which I don't want.
Below is the environment I've been using to experiment, the touches moved gives the correct type of control I want although I can't seem to figure out how to stop the sprite first moving to the location of the touch before the swipe/drag
import SpriteKit
import GameplayKit
var player = SKSpriteNode()
var playerColor = UIColor.orange
var background = UIColor.black
var playerSize = CGSize(width: 50, height: 50)
var touchLocation = CGPoint()
var shape = CGPoint()
class GameScene: SKScene {
override func didMove(to view: SKView) {
self.backgroundColor = background
spawnPlayer()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let touchLocation = touch.location(in: self)
player.position.x = touchLocation.x
}
}
func spawnPlayer(){
player = SKSpriteNode(color: playerColor, size: playerSize)
player.position = CGPoint(x:50, y: 500)
self.addChild(player)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
In summary, I'm looking for the same method of controlling a sprite as in snakes vs blocks
Hey so you have the right idea, you need to store a previous touchesMovedPoint. Then use it to determine how far your finger has moved each time touchesMoved has been called. Then add to the player's current position by that quantity.
var lastTouchLoc:CGPoint = CGPoint()
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let touchLocation = touch.location(in: self)
let dx = touchLocation.x - lastTouchLoc.x
player.position.x += dx // Change the players current position by the distance between the previous touchesMovedPoint and the current one.
lastTouchLoc.x = touchLocation.x // Update last touch location
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
// When releasing the screen from point "E" we don't want dx being calculated from point "E".
// We want dx to calculate from the most recent touchDown point
let initialTouchPoint = touch.location(in: self)
lastTouchLoc.x = initialTouchPoint.x
}
}
You want to use SKAction.move to perform what you want.
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
return hypot(lhs.x.distance(to: rhs.x), lhs.y.distance(to: rhs.y))
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let touchLocation = touch.location(in: self)
let speed = 100/1 //This means 100 points per second
let duration = distance(player.position,touchLocation) / speed //Currently travels 100 points per second, adjust if needed
player.run(SKAction.move(to:touchLocation, duration:duration),forKey:"move")
}
}
What this does is it will move your "snake" to the new location at a certain duration. To determine duration, you need to figure out how fast you want your "snake" to travel. I currently have it set up to where it should take 1 second to move 100 points, but you may want to adjust that as needed.
I have really strange behavior with function removeFromParent
lazy var buttonAds: SKSpriteNode = {
let n = SKSpriteNode(imageNamed: "ButtonAds")
n.position = CGPoint(x: size.width / 2, y: 600)
n.zPosition = 100
n.setScale(1.4)
return n
}()
in didMove(...) add this button with addChild(buttonAds), and latter in touchesBegan:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
if buttonAds.contains(touch.location(in: self)) {
// ...
doAds()
buttonAds.removeFromParent()
}
}
If you tap on button for ads, will be removed, but if tap on that place again, this will call function doAds() again... it's strange, buttonAd don't exist on scene.
Initial:
and after tap:
Thanks
What you want to do is check if the node you touch is of the type it should be. Change your code to this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
if nodeAtPoint(touch.locationInNode(self)) == buttonAds {
doAds()
buttonAds.removeFromParent()
}
}
This should do the trick!
edit: as to why this works, you're removing the node from the scene but it is still an object in memory (otherwise you wouldn't be able to use buttonAds.contains(...) on it) so it also still has its position stored.
I need to make an icon rotate depending on the current rotation of another UIImageview. For that I have one UIImageView which movement is controlled by a rotation user gesture. I also have an UIView which contains icons that I need to rotate properly. This UIView have always the exact same position and rotation as the UIImageview.
Graphically this is what I need to do:
I manage the motion with this code:
I managed the gesture with the following code:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
origin = (touches.first?.locationInView(self.view))!
xOffSet = CGVector(dx:(origin.x)-rotateImageView.center.x, dy:(origin.y) - rotateImageView.center.y)
startingAngle = atan2(xOffSet.dy,xOffSet.dx)
//save the current transform
tempTransform = rotateImageView.transform
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
if touch!.view === rotateImageView || touch!.view === viewIcons {
let touchPoint = touches.first?.locationInView(self.view)
yOffSet = CGVector(dx:touchPoint!.x - rotateImageView.center.x, dy:touchPoint!.y - rotateImageView.center.y)
let angle = atan2(yOffSet.dy,yOffSet.dx)
let deltaAngle = angle - startingAngle!
rotateImageView.transform = CGAffineTransformRotate(tempTransform, deltaAngle)
viewIcons.transform = CGAffineTransformRotate(tempTransform, deltaAngle)
rotateIconsWithImageView (ivClock , angle: deltaAngle)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
startingAngle = nil
}
//reset in case drag is cancelled
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
rotateImageView.transform = tempTransform
startingAngle = nil
}
This code prevents another problem which happens when the user starts the gesture on another section of the screen. Detailed information can be seen on this another stackoverflow thread: Rotation gesture produces undesired rotation
For the rotation if the icon I use this function which I invoke as the last instruction on touchesMoved:
func rotateIconsWithImageView (imageview:UIImageView , angle: CGFloat ) {
imageview.transform = CGAffineTransformRotate(tempTransform, -angle)
}
This works fine normally (as you see on the gif) if I start the gesture without changing the touch area. But If the user changes the touch area the rotation is wrong and since I'm not used with transform I'm not sure what exactly to do.
This gif illustrate the issue:
Thanks in advance
Get rid of your tempTransform. It's just confusing you. The transform on the outer wheel is not the same as the transform on the icon, so it is nutty to mix them up like this:
func rotateIconsWithImageView (imageview:UIImageView , angle: CGFloat ) {
imageview.transform = CGAffineTransformRotate(tempTransform, -angle)
}
Instead, just keep rotating the transform of the icon:
func rotateIconsWithImageView (imageview:UIImageView , angle: CGFloat ) {
imageview.transform = CGAffineTransformRotate(imageview.transform, -angle)
}
When using SpriteKit in watchOS 3, how do you handle the touch events? I am porting the SpriteKit games from iOS and the codes below won't work. Or you have to control the WKInterfaceController somehow?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {}
After a lot of frustration with the same issue, I wound up using gesture recognizers, and calculating the position from there.
#IBAction func handleSingleTap(tapGesture: WKTapGestureRecognizer) {
let location = tapGesture.locationInObject()
if yourSpriteNodeRect.contains(location) {
//do stuff
}
}
One tip though: A strange bug I found though when creating the method to handle the gesture recognizer was that the gesture wasn't being passed by default into the method. I had to ctrl + drag to create the method, and then modify the signature to contain the gesture. If I created the method with the gesture in the signature, Xcode wouldn't allow me to attach the gesture recognizer to it.
It would seem that there would be a better way to detect the touches, but that's the workaround I found for now.
To get something close to touch down events use a WKLongPressGestureRecognizer with super-short duration (0.001). The IBAction connected to this recognizer will get touch down events pretty much right away. The location is found in the locationInObject property of the recognizer. If the goal is to get moving touches then use the pan recognizer.
So to do this I first hooked up a gesture recognizer in the watch storyboard. Created an IBAction, then called a method on the SpriteKit Scene passing in gesture.locationInObject(). Then within the scene method for the conversion, here's some code.
let screenBounds = WKInterfaceDevice.current().screenBounds
let newX = ((location.x / screenBounds.width) * self.size.width) - (self.size.width / 2)
let newY = -(((location.y / screenBounds.height) * self.size.height) - (self.size.height / 2))
The newX and newY are the touch coordinates in the scene.
How can I recognize continuous user touch in Swift code? By continuous I mean that the user has her finger on the screen. I would like to move a sprite kit node to the direction of user's touch for as long as the user is touching screen.
The basic steps
Store the location of the touch events (touchesBegan/touchesMoved)
Move sprite node toward that location (update)
Stop moving the node when touch is no longer detected (touchesEnded)
Here's an example of how to do that
Xcode 8
let sprite = SKSpriteNode(color: SKColor.white, size: CGSize(width:32, height:32))
var touched:Bool = false
var location = CGPoint.zero
override func didMove(to view: SKView) {
/* Add a sprite to the scene */
sprite.position = CGPoint(x:0, y:0)
self.addChild(sprite)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touched = true
for touch in touches {
location = touch.location(in:self)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
location = touch.location(in: self)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// Stop node from moving to touch
touched = false
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if (touched) {
moveNodeToLocation()
}
}
// Move the node to the location of the touch
func moveNodeToLocation() {
// Compute vector components in direction of the touch
var dx = location.x - sprite.position.x
var dy = location.y - sprite.position.y
// How fast to move the node. Adjust this as needed
let speed:CGFloat = 0.25
// Scale vector
dx = dx * speed
dy = dy * speed
sprite.position = CGPoint(x:sprite.position.x+dx, y:sprite.position.y+dy)
}
Xcode 7
let sprite = SKSpriteNode(color: SKColor.whiteColor(), size: CGSizeMake(32, 32))
var touched:Bool = false
var location = CGPointMake(0, 0)
override func didMoveToView(view: SKView) {
self.scaleMode = .ResizeFill
/* Add a sprite to the scene */
sprite.position = CGPointMake(100, 100)
self.addChild(sprite)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Start moving node to touch location */
touched = true
for touch in touches {
location = touch.locationInNode(self)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Update to new touch location */
for touch in touches {
location = touch.locationInNode(self)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Stop node from moving to touch
touched = false
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if (touched) {
moveNodeToLocation()
}
}
// Move the node to the location of the touch
func moveNodeToLocation() {
// How fast to move the node
let speed:CGFloat = 0.25
// Compute vector components in direction of the touch
var dx = location.x - sprite.position.x
var dy = location.y - sprite.position.y
// Scale vector
dx = dx * speed
dy = dy * speed
sprite.position = CGPointMake(sprite.position.x+dx, sprite.position.y+dy)
}
The most difficult thing about this process is tracking single touches within a multitouch environment. The issue with the "simple" solution to this (i.e., turn "istouched" on in touchesBegan and turn it off in touchesEnded) is that if the user touches another finger on the screen and then lifts it, it will cancel the first touch's actions.
To make this bulletproof, you need to track individual touches over their lifetime. When the first touch occurs, you save the location of that touch and move the object towards that location. Any further touches should be compared to the first touch, and should be ignored if they aren't the first touch. This approach also allows you to handle multitouch, where the object could be made to move towards any finger currently on the screen, and then move to the next finger if the first one is lifted, and so on.
It's important to note that UITouch objects are constant across touchesBegan, touchesMoved, and touchesEnded. You can think of a UITouch object as being created in touchesBegan, altered in touchesMoved, and destroyed in touchesEnded. You can track the phase of a touch over the course of its life by saving a reference to the touch object to a dictionary or an array as it is created in touchesBegan, then in touchesMoved you can check the new location of any existing touches and alter the object's course if the user moves their finger (you can apply tolerances to prevent jitter, e.g., if the x/y distance is less than some tolerance, don't alter the course). In touchesEnded you can check if the touch in focus is the one that ended, and cancel the object's movement, or set it to move towards any other touch that is still occurring. This is important, as if you just check for any old touch object ending, this will cancel other touches as well, which can produce unexpected results.
This article is in Obj-C, but the code is easily ported to Swift and shows you what you need to do, just check out the stuff under "Handling a Complex Multitouch Sequence": https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/multitouch_background/multitouch_background.html
Below is the code to drag the node around on X position (left and right), it is very easy to add Y position and do the same thing.
let item = SKSpriteNode(imageNamed: "xx")
var itemXposition = 50
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// updates itemXposition variable on every touch
for touch in touches {
let location = touch.location(in: self)
itemXposition = Int(location.x)
}
}
// this function is called for each frame render, updates the position on view
override func update(_ currentTime: TimeInterval) {
spaceShip.position = CGPoint(x: self.itemXposition , y: 50 )
}