Makes an UIImageview rotate depending on another UIImageview rotation - swift

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

Related

How to control sprite using swiping gestures so sprite can move along x axis

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.

removeFromParent() strange behavior

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.

SpriteKit tracking multiple touches

I have several buttons that move a main character. (Left, right, jump, etc.) But, when I touch more than one button at a time, the previous one is ended. My question is, how do I keep both touches alive for the duration of their touches? As an example, this would allow the character to move forward and jump at the same time. I have set multipleTouchEnabled to true. I've read that using a dictionary to track touches would help, but I can't seem to wrap my head around the implementation.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInView(nil)
if location.x < self.size.width / 2 && location.y > self.size.height / 2 {
movingLeft = true
}
if location.x > self.size.width / 2 && location.y > self.size.height / 2 {
movingRight = true
}
if location.x < self.size.width / 2 && location.y < self.size.height / 2 {
jump()
}
if location.x > self.size.width / 2 && location.y < self.size.height / 2 {
jump()
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
movingLeft = false
movingRight = false
}
func jump() {
mainCharacter.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
mainCharacter.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 400))
}
The logic
You should create a dictionary of active touches.
private var activeTouches = [UITouch:String]()
Every time a touch begins you save it into the dictionary and assign to it a label.
activeTouches[touch] = "left"
So when the touch does end you can search for it into your dictionary and find the related label. Now you know which button has been released by the user.
let button = activeTouches[touch]
if button == "left" { ... }
And don't forget to remove it from the dictionary.
activeTouches[touch] = nil
The implementation
class GameScene: SKScene {
private var activeTouches = [UITouch:String]()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let button = findButtonName(from:touch)
activeTouches[touch] = button
tapBegin(on: button)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
guard let button = activeTouches[touch] else { fatalError("Touch just ended but not found into activeTouches")}
activeTouches[touch] = nil
tapEnd(on: button)
}
}
private func tapBegin(on button: String) {
print("Begin press \(button)")
// your custom logic goes here
}
private func tapEnd(on button:String) {
print("End press \(button)")
// your custom logic goes here
}
private func findButtonName(from touch: UITouch) -> String {
// replace this with your custom logic to detect a button location
let location = touch.locationInView(self.view)
if location.x > self.view?.frame.midX {
return "right"
} else {
return "left"
}
}
}
In the code above you should put your own code into
tapBegin: this method receive the label of a button and start some action.
E.g. start running.
tapEnd: this method receive the label of a button and stop some action.
E.g. stop running.
findButtonName: this method receives a UITouch and returns the label of the button pressed by the user.
Test
I tested the previous code on my iPhone. I performed the following actions.
started pressing the right of the screen
started pressing the left of the screen
removed finger from the right of the screen
removed finger from the left of the screen
As you can see in the following log the code is capable of recognizing different touches
Begin press right
Begin press left
End press right
End press left
Conclusion
I hope I made myself clear. Let me know if something is not.
Issue
For me it was a bit foolish but there's an option that I miss to enable in my storyboard in order to handle multiple touch in my GameScene of type SKScene.
When one of the overwritten functions is being called, you should have more than 1 touch contained in touches.
Those functions are indeed:
touchesBegan
touchesMoved
touchesEnded
touchesCancelled
... and the way to retrieve the touches is indeed to browse through them. However, if you are only able to retrieve one touch only, follow the steps bellow.
Debugging
Here's what you can do to see if you have multiple touches in your set of touches:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for (i, t) in touches.enumerated() {
print(i, t.location(in: self))
self.touchMoved(toPoint: t.location(in: self))
}
}
Output:
There you should find a 0 for the first touch and a 1 when you touch with 2 fingers... and so on.
Solution
I case you encounter the same problem than me and have only the touch t at index 0 but no other touch, you should see upper in the hierarchy of views if all of them are enabled for multi-touch. In my case it was the view containing the GameScene of type SKScene that did not have the option Multiple Touch enabled. See below:

How to recognize continuous touch in Swift?

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

iOS dragging UIImageView

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