Move marker between multiple locations with animation on Google map - ios

I want to move a car icon between two location coordinates and i want to change the duration(speed) of that car icon by slider.
i tried CATransaction to animate the marker.Its animating fine but i am not able to change the duration(speed) at real time by slider.
Here is my code for animating the marker.
func moveTo(point: CLLocationCoordinate2D) {
if let marker = arrMapList[0] as? GMSMarker {
let angle = getAngle(fromLoaction: marker.position, toLocation: points[currentIndex])
marker.rotation = CLLocationDegrees(angle * (180.0 / .pi))
CATransaction.begin()
CATransaction.setAnimationDuration(5)
CATransaction.setCompletionBlock {
if let nextPoint = self.getNextPoint(currentIndex: self.currentIndex) {
self.moveTo(point: nextPoint)
}
}
marker.position = point
CATransaction.commit()
}
}
I tried to change duration as follow but its not working.
#IBAction func SliderValueChanged(_ sender: UISlider) {
CATransaction.setAnimationDuration(CFTimeInterval(sender.value))
}
Marker(car) icon should be move between two coordinate and if user change the value of speed by slider moving left or right, Animation duration should be effected.

You can't change the duration of an in-flight animation.
You should be able to change the layer's speed parameter to speed it up.
Speed = 1.0 is normal speed. Speed 2.0 would be twice as fast (half the duration). A speed of 0.5 would be half-speed.
You'd use code like
marker.layer.speed = 2.0
(You'll need to refactor to set the speed from the slider. Try a range from 0.5 to 1.5.)

Related

How to rotate a SCNSphere using a pan gesture recognizer

I created an SCNSphere so now it looks like a planet kind of. This is exactly what I want. My next goal is to allow users to rotate the sphere using a pan gesture recognizer. They are allowed to rotate it around the X or Y axis. I was just wondering how I can do that. This is what I have so far.
origin = sceneView.frame.origin
node.geometry = SCNSphere(radius: 1)
node.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "world.jpg")
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(CategoryViewController.panGlobe(sender:)))
sceneView.addGestureRecognizer(panGestureRecognizer)
func panGlobe(sender: UIPanGestureRecognizer) {
// What should i put inside this method to allow them to rotate the sphere/ball
}
We have a ViewController that contains a node sphereNode that contains our sphere.
To rotate the sphere we could use a UIPanGestureRecognizer.
Since the recognizer reports the total distance our finger has traveled on the screen we cache the last point that was reported to us.
var previousPanPoint: CGPoint?
let pixelToAngleConstant: Float = .pi / 180
func handlePan(_ newPoint: CGPoint) {
if let previousPoint = previousPanPoint {
let dx = Float(newPoint.x - previousPoint.x)
let dy = Float(newPoint.y - previousPoint.y)
rotateUp(by: dy * pixelToAngleConstant)
rotateRight(by: dx * pixelToAngleConstant)
}
previousPanPoint = newPoint
}
We calculate dx and dy with how much pixel our finger has traveled in each direction since we last called the recognizer.
With the pixelToAngleConstant we convert our pixel value in an angle (in randians) to rotate our sphere. Use a bigger constant for a faster rotation.
The gesture recognizer returns a state that we can use to determine if the gesture has started, ended, or the finger has been moved.
When the gesture starts we save the fingers location in previousPanPoint.
When our finger moves we call the function above.
When the gesture is ended or canceled we clear our previousPanPoint.
#objc func handleGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
switch gestureRecognizer.state {
case .began:
previousPanPoint = gestureRecognizer.location(in: view)
case .changed:
handlePan(gestureRecognizer.location(in: view))
default:
previousPanPoint = nil
}
}
How do we rotate our sphere?
The functions rotateUp and rotateRight just call our more general function, rotate(by: around:) which accepts not only the angle but also the axis to rotate around.
rotateUp rotates around the x-axis, rotateRight around the y-axis.
func rotateUp(by angle: Float) {
let axis = SCNVector3(1, 0, 0) // x-axis
rotate(by: angle, around: axis)
}
func rotateRight(by angle: Float) {
let axis = SCNVector3(0, 1, 0) // y-axis
rotate(by: angle, around: axis)
}
The rotate(by:around:) is in this case relative simple because we assume that the node is not translated/ we want to rotate around the origin of the nodes local coordinate system.
Everything is a little more complicated when we look at a general case but this answer is only a small starting point.
func rotate(by angle: Float, around axis: SCNVector3) {
let transform = SCNMatrix4MakeRotation(angle, axis.x, axis.y, axis.z)
sphereNode.transform = SCNMatrix4Mult(sphereNode.transform, transform)
}
We create a rotation matrix from the angle and the axis and multiply the old transform of our sphere with the calculated one to get the new transform.
This is the little demo I created:
This approach has two major downsides.
It only rotates around the nodes coordinate origin and only works properly if the node's position is SCNVector3Zero
It does takes neither the speed of the gesture into account nor does the sphere continue to rotate when the gesture stops.
An effect similar to a table view where you can flip your finger and the table view scrolls fast and then slows down can't be easily achieved with this approach.
One solution would be to use the physics system for that.
Below is what I tried, not sure whether it is accurate with respect to angles but...it sufficed most of my needs....
#objc func handleGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: gestureRecognizer.view!)
let x = Float(translation.x)
let y = Float(-translation.y)
let anglePan = (sqrt(pow(x,2)+pow(y,2)))*(Float)(Double.pi)/180.0
var rotationVector = SCNVector4()
rotationVector.x = x
rotationVector.y = y
rotationVector.z = 0.0
rotationVector.w = anglePan
self.earthNode.rotation = rotationVector
}
Sample Github-EarthRotate

Rotate sprite node with moving touch

I am new to Swift and SpriteKit and am learning to understand the control in the game "Fish & Trip". The sprite node is always at the center of the view and it will rotate according to moving your touch, no matter where you touch and move (hold) it will rotate correspondingly.
The difficulty here is that it is different from the Pan Gesture and simple touch location as I noted in the picture 1 and 2.
For the 1st pic, the touch location is processed by atan2f and then sent to SKAction.rotate and it is done, I can make this working.
For the 2nd pic, I can get this by setup a UIPanGestureRecognizer and it works, but you can only rotate the node when you move your finger around the initial point (touchesBegan).
My question is for the 3rd pic, which is the same as the Fish & Trip game, you can touch anywhere on the screen and then move (hold) to anywhere and the node still rotate as you move, you don't have to move your finger around the initial point to let the node rotate and the rotation is smooth and accurate.
My code is as follow, it doesn't work very well and it is with some jittering, my question is how can I implement this in a better way? and How can I make the rotation smooth?
Is there a way to filter the previousLocation in the touchesMoved function? I always encountered jittering when I use this property, I think it reports too fast. I haven't had any issue when I used UIPanGestureRecoginzer and it is very smooth, so I guess I must did something wrong with the previousLocation.
func mtoRad(x: CGFloat, y: CGFloat) -> CGFloat {
let Radian3 = atan2f(Float(y), Float(x))
return CGFloat(Radian3)
}
func moveplayer(radian: CGFloat){
let rotateaction = SKAction.rotate(toAngle: radian, duration: 0.1, shortestUnitArc: true)
thePlayer.run(rotateaction)
}
var touchpoint = CGPoint.zero
var R2 : CGFloat? = 0.0
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches{
let previousPointOfTouch = t.previousLocation(in: self)
touchpoint = t.location(in: self)
if touchpoint.x != previousPointOfTouch.x && touchpoint.y != previousPointOfTouch.y {
let delta_y = touchpoint.y - previousPointOfTouch.y
let delta_x = touchpoint.x - previousPointOfTouch.x
let R1 = mtoRad(x: delta_x, y: delta_y)
if R2! != R1 {
moveplayer(radiant: R1)
}
R2 = R1
}
}
}
This is not an answer (yet - hoping to post one/edit this into one later), but you can make your code a bit more 'Swifty' by changing the definition for movePlayer() from:
func moveplayer(radian: CGFloat)
to
rotatePlayerTo(angle targetAngle: CGFloat) {
let rotateaction = SKAction.rotate(toAngle: targetAngle, duration: 0.1, shortestUnitArc: true)
thePlayer.run(rotateaction)
}
then, to call it, instead of:
moveplayer(radiant: R1)
use
rotatePlayerTo(angle: R1)
which is more readable as it describes what you are doing better.
Also, your rotation to the new angle is constant at 0.1s - so if the player has to rotate further, it will rotate faster. it would be better to keep the rotational speed constant (in terms of radians per second). we can do this as follows:
Add the following property:
let playerRotationSpeed = CGFloat((2 *Double.pi) / 2.0) //Radian per second; 2 second for full rotation
change your moveShip to:
func rotatePlayerTo(angle targetAngle: CGFloat) {
let angleToRotateBy = abs(targetAngle - thePlayer.zRotation)
let rotationTime = TimeInterval(angleToRotateBy / shipRotationSpeed)
let rotateAction = SKAction.rotate(toAngle: targetAngle, duration: rotationTime , shortestUnitArc: true)
thePlayer.run(rotateAction)
}
this may help smooth the rotation too.

Snapchat-like text on image

I have been trying to implement a Snapchat-like edit text on an image.
What I did so far is implement a UILabel in the center of the UIImageView and I added 3 gestures to this UILabel: UIPanGestureRecognizer, UIPinchGestureRecognizer & UIRotationGestureRecognizer.
I have managed to implement the Pan method, but I am having hard time to make the Pinch + Rotation as smooth as they do, I get horrible results T_T
How do you guys think this was made? which components are involved in this & if you have any reading / watching material I could use to accomplish this.
Thanks :)
EDIT:
These are the methods I implemented to handle Pinch & Rotation:
func handlePinch(recognizer: UIPinchGestureRecognizer) {
if let view = recognizer.view as? UILabel {
view.transform = CGAffineTransformScale(view.transform, recognizer.scale, recognizer.scale)
}
}
func handleRotate(recognizer: UIRotationGestureRecognizer) {
if let view = recognizer.view as? UILabel {
view.transform = CGAffineTransformRotate(view.transform, recognizer.rotation)
}
}
Preview video of how the pinch I implemented works:
https://drive.google.com/file/d/0B-AVM51jxsvUY2RUUHdWbGo5QlU/view?usp=sharing
Found a solution, apparently all I needed to do in the Rotation & Pinch is to always reset the view's rotation / scale.
For instance, setting my recognizer.scale to 1.0 and recognizer.rotation to 0.0.
Here is an example of my code:
func handlePan(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPointZero, inView: view)
}
func handlePinch(recognizer: UIPinchGestureRecognizer) {
if let view = recognizer.view as? UILabel {
let pinchScale: CGFloat = recognizer.scale
view.transform = CGAffineTransformScale(view.transform, pinchScale, pinchScale)
recognizer.scale = 1.0
}
}
func handleRotate(recognizer: UIRotationGestureRecognizer) {
if let view = recognizer.view as? UILabel {
let rotation: CGFloat = recognizer.rotation
view.transform = CGAffineTransformRotate(view.transform, rotation)
recognizer.rotation = 0.0
}
}
For "it scales up / down way too much in a very aggressive way":
You need to handle the pinch gesture scale value according to your need.
From your recogniser method, you get the scale value as:
var pinchScale: CGFloat = recogniser.scale
You need to modify this value either like decrease it by /10 or /100 as per your need, and use this value in the label transformation instead of using the pangesture scale.
The issue you have is that your code takes the current transform and adds another transform based on the current "movement", so you accumulate changes (compound them, really) as you move during a single gesture.
Keep instance variables for rotation, scale, and movement, update the relevant one in each of your gesture recognizer's actions (you'll also need to store the state of each at the beginning of each gesture, so you can apply the delta to the initial state), and create the transform from scratch using those three variables. The transform creating should of course be factorized in a separate function, since you're going to use it several times.
I'm not sure if this is exactly what you're looking for (I've never used snapchat), but this could give you some ideas
https://github.com/danielinoa/DIImageView
I'm not sure this one has a pinch gesture, but I'm not entirely sure how you mean it to be used anyway.
Here is a demo of that project:
https://www.cocoacontrols.com/controls/diimageview
In general, I recommend checking cocoacontrols every time you venture to implement something like this. There are usually solid examples to get you started, and sometimes, you'll find exactly what you need.

Repeating animation on tap

I want to rotate a UIImage, I have managed to do so with the code below, however when I press the rotate button again, the image does not rotate anymore, could someone please explain why?
#IBAction func rotate(sender: UIButton) {
UIView.animateWithDuration(0.2, animations: {
self.shape.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4) * 2)
})
}
You are changing your shape image view's transform to a new, fixed value. If you tap on it again, the transform already has that value. You set the transform to the same value again, which doesn't change anything.
You need to define an instance variable to keep track of the rotation.
var rotation: CGFloat = 0
#IBAction func rotate(sender: UIButton)
{
UIView.animateWithDuration(0.2, animations:
{
self.rotation += CGFloat(M_PI_4) * 2 //changed based on Daniel Storm's comment
self.shape.transform = CGAffineTransformMakeRotation(rotation)
})
}
That way, each time your tap the button you'll change the rotation variable from it's previous value to a new value and rotate to that new angle.

Zoom SKSpriteNode in AND out in Swift

First off, I have already seen and tried to implement the other answers to similar questions here, here and here. The problem is I started programming for iOS last year with Swift and (regrettably) I did not learn ObjC first (yes, it's now on my to-do list). ;-)
So please take a look and see if you might help me see my way thru this.
I can easily pinch to zoom the whole SKScene. I can also scale an SKSpiteNode up/down by using other UI Gestures (ie. swipes) and SKActions.
Based off this post I have applied the SKAction to the UIPinchGestureRecognizer and it works perfectly to zoom IN, but I cannot get it to zoom back OUT.
What am I missing?
Here is my code on a sample project:
class GameScene: SKScene {
var board = SKSpriteNode(color: SKColor.yellowColor(), size: CGSizeMake(200, 200))
func pinched(sender:UIPinchGestureRecognizer){
println("pinched \(sender)")
// the line below scales the entire scene
//sender.view!.transform = CGAffineTransformScale(sender.view!.transform, sender.scale, sender.scale)
sender.scale = 1.01
// line below scales just the SKSpriteNode
// But it has no effect unless I increase the scaling to >1
var zoomBoard = SKAction.scaleBy(sender.scale, duration: 0)
board.runAction(zoomBoard)
}
// line below scales just the SKSpriteNode
func swipedUp(sender:UISwipeGestureRecognizer){
println("swiped up")
var zoomBoard = SKAction.scaleBy(1.1, duration: 0)
board.runAction(zoomBoard)
}
// I thought perhaps the line below would scale down the SKSpriteNode
// But it has no effect at all
func swipedDown(sender:UISwipeGestureRecognizer){
println("swiped down")
var zoomBoard = SKAction.scaleBy(0.9, duration: 0)
board.runAction(zoomBoard)
}
override func didMoveToView(view: SKView) {
self.addChild(board)
let pinch:UIPinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: Selector("pinched:"))
view.addGestureRecognizer(pinch)
let swipeUp:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedUp:"))
swipeUp.direction = .Up
view.addGestureRecognizer(swipeUp)
let swipeDown:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedDown:"))
swipeDown.direction = .Down
view.addGestureRecognizer(swipeDown)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// should I be using this function instead?
}
Thanks to the help from #sangony I have gotten this working finally. I thought I'd post the working code in case anyone else would like to see it in Swift.
var board = SKSpriteNode(color: SKColor.yellowColor(), size: CGSizeMake(200, 200))
var previousScale = CGFloat(1.0)
func pinched(sender:UIPinchGestureRecognizer){
if sender.scale > previousScale {
previousScale = sender.scale
if(board.size.height < 800) {
var zoomIn = SKAction.scaleBy(1.05, duration:0)
board.runAction(zoomIn)
}
}
if sender.scale < previousScale {
previousScale = sender.scale
if(board.size.height > 200) {
var zoomOut = SKAction.scaleBy(0.95, duration:0)
board.runAction(zoomOut)
}
}
I tried your code (in Objective C) and got it to zoom in and out using pinch. I don't think there's anything wrong with your code but you are probably not taking into account the scale factors as they are placed on the ever changing sprite size.
You can easily zoom so far out or in that it requires multiple pinch gestures to get the node back to a manageable size. Instead of using the scale property directly for your zoom factor, I suggest you use a step process. You should also have max/min limits for your scale size.
To use the step process you create a CGFloat ivar previousScale to store the last scale value as to determine whether the current pinch is zooming in or out. You then compare the new passed sender.scale to the ivar and zoom in or out based on the comparison.
Apply min and max scale limits to stop scaling once they are reached.
The code below is in Obj-C but I'm sure you can get the gist of it:
First declare your ivar float float previousScale;
- (void)handlePinch:(UIPinchGestureRecognizer *)sender {
NSLog(#"pinchScale:%f",sender.scale);
if(sender.scale > previousScale) {
previousScale = sender.scale;
// only scale up if the node height is less than 200
if(node0.size.height < 200) {
// step up the scale factor by 0.05
[node0 runAction:[SKAction scaleBy:1.05 duration:0]];
}
}
if(sender.scale < previousScale) {
previousScale = sender.scale;
// only scale down if the node height is greater than 20
if(node0.size.height > 20) {
// step down the scale factor by 0.05
[node0 runAction:[SKAction scaleBy:0.95 duration:0]];
}
}
}

Resources