In Swift: How to use uislider value as a variable to set rotation speed - uislider

I have successfully printed the value of a UISlider in a label using the code below:
#IBAction func valueChanged(_ sender: UISlider) {
var currentValue: String = String(Int(sender.value))
statusLabel.text = currentValue
}
Label showing UISlider value.
What I want to do now is to use this value as the variable “a” in the code below which sets the duration of rotation of a sprite.
let rotation = SKAction.rotate(byAngle: .pi * -1, duration: a)
let infiniteRotation = SKAction.repeatForever(rotation)
centerNode.run(infiniteRotation)
Thank You.

Just use a global variable...
var timeAmount = 0
#IBAction func sliderValueChanged(sender: UISlider) {
timeAmount = Int(sender.value)
println("Slider and timeAmount changing to \(timeAmount) ?")
}
and then in your function for the rotation, just use:
func __somefunction__(some params){
...
let rotation = SKAction.rotate(byAngle: .pi * -1, duration: timeAmount)
let infiniteRotation = SKAction.repeatForever(rotation)
centerNode.run(infiniteRotation)
...
}

Related

Shake animation of NSView

I am trying to implement a shaking NSView in my macOS app.
I tried following the example/tutorial from here, but I am not having much success.
I am attempting to not only shake the view left to right, but also up and down, and with a 2 degree positive and negative rotation.
I thought I could nest animations through the completion handler within a loop, but that is not working the way I expected so I am trying to use an if statement to implement the shake, which also is not working.
The intent here is to simulate a shake from an explosion and as the explosions get closer the intensity increases which would cause the shaking to increase in the number of shakes as well as the duration of the view shaking.
Here's example code I am working with to master this task:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var shakeButton1: NSButton!
#IBOutlet weak var shakeButton2: NSButton!
#IBOutlet weak var shakeButton3: NSButton!
#IBOutlet weak var shakeButton4: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func shakeButtonClicked(button: NSButton) {
switch button.identifier {
case shakeButton1.identifier:
shakeView(on: self.view, shakes: 2)
case shakeButton2.identifier:
shakeView(on: self.view, shakes: 4)
case shakeButton3.identifier:
shakeView(on: self.view, shakes: 6)
case shakeButton4.identifier:
shakeView(on: self.view, shakes: 8)
default: break
}
}
/**
Method simulates a shaking of a view
intensity value is an integer for the number of times the animation is repeated.
*/
func shakeView(on theView: NSView, shakes: Int) {
let originalOrigin = self.view.frame.origin
let rotation: CGFloat = 2
let moveValue: CGFloat = 5
let contextDuration: Double = 0.05
for shake in 0..<shakes {
if shake.isMultiple(of: 2) {
print("Even")
// Perform the first animation
NSAnimationContext.runAnimationGroup({ firstAnimation in
firstAnimation.duration = contextDuration
// Animate to the new origin and angle
theView.animator().frame.origin.x = moveValue // positive origin moves right
theView.animator().frame.origin.y = moveValue // positive origin moves up
theView.animator().rotate(byDegrees: -rotation) // apply a negative rotation
print("First rotation \(shake) of \(shakes) = \(self.view.animator().frameRotation)")
}) { // First completion handler
theView.frame.origin = originalOrigin
theView.rotate(byDegrees: rotation) // apply a positive rotation to rotate the view back to the original position
print("Second rotation \(shake) of \(shakes) = \(self.view.frameRotation)")
}
} else {
print("Odd")
// Perform the opposite action
NSAnimationContext.runAnimationGroup({ secondAnimation in
secondAnimation.duration = contextDuration
theView.animator().frame.origin.x = -moveValue // negative origin moves left
theView.animator().frame.origin.y = -moveValue // negative origin moves down
theView.animator().rotate(byDegrees: rotation) // apply positive rotation
print("Third rotation \(shake) of \(shakes) = \(self.view.frameRotation)")
}) { // Second completion handler
theView.frame.origin = originalOrigin
theView.rotate(byDegrees: -rotation) // apply a negative rotation to rotate the view back to the original position
print("Fourth rotation \(shake) of \(shakes) = \(self.view.frameRotation)")
}
}
}
}
}
I set up four buttons in storyboard and linked them all to the same IBAction method for simplicity. Button identifiers in storyboard are "shake1Button", "shake2Button", "shake3Button", and "shake4Button".
Thank you for any help.
EDIT
I think I nearly have it. The only issue I am now having is when the rotation occurs, the center of rotation does not appear to be centered on the view. I am now using NotificationCenter to handle the animation. It looks pretty good the way it is, but I would really like to get the center of rotation set on the center of the view.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var shake1Button: NSButton!
#IBOutlet weak var shake2Button: NSButton!
#IBOutlet weak var shake3Button: NSButton!
#IBOutlet weak var shake4Button: NSButton!
let observatory = NotificationCenter.default
var shaken: CGFloat = 0
var intensity: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupObservatory()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func shakeButtonClicked(button: NSButton) {
switch button.identifier {
case shake1Button.identifier:
intensity = 1
case shake2Button.identifier:
intensity = 2
case shake3Button.identifier:
intensity = 3
case shake4Button.identifier:
intensity = 4
default: break
}
observatory.post(name: .shakeView, object: nil)
}
func setupObservatory() {
observatory.addObserver(forName: .shakeView, object: nil, queue: nil, using: shakeView)
observatory.addObserver(forName: .moveDownAnimation, object: nil, queue: nil, using: moveDownAnimation)
observatory.addObserver(forName: .moveLeftAnimation, object: nil, queue: nil, using: moveLeftAnimation)
observatory.addObserver(forName: .moveRightAnimation, object: nil, queue: nil, using: moveRightAnimation)
observatory.addObserver(forName: .moveUpAnimation, object: nil, queue: nil, using: moveUpAnimation)
observatory.addObserver(forName: .rotateLeftAnimation, object: nil, queue: nil, using: rotateLeftAnimation)
observatory.addObserver(forName: .rotateRightAnimation, object: nil, queue: nil, using: rotateRightAnimation)
}
func shakeView(notification: Notification) {
if shaken != intensity {
shaken += 1
observatory.post(name: .moveDownAnimation, object: nil)
} else {
shaken = 0
}
}
func moveDownAnimation(notification: Notification) {
//guard let theView = notification.object as? NSView else { return }
let originalOrigin = self.view.frame.origin
NSAnimationContext.runAnimationGroup({ verticalAnimation in
verticalAnimation.duration = 0.05
// Animate to the new angle
self.view.animator().frame.origin.y -= intensity // subtracting value moves the view down
}) { // Completion handler
self.view.frame.origin = originalOrigin
self.observatory.post(name: .moveLeftAnimation, object: nil)
}
}
func moveLeftAnimation(notification: Notification) {
//guard let theView = notification.object as? NSView else { return }
let originalOrigin = self.view.frame.origin
// Perform the animation
NSAnimationContext.runAnimationGroup({ firstAnimation in
firstAnimation.duration = 0.05
// Animate to the new origin
self.view.animator().frame.origin.x -= intensity // subtracting value moves the view left
}) { // Completion handler
self.view.frame.origin = originalOrigin
self.observatory.post(name: .rotateLeftAnimation, object: nil)
}
}
func moveRightAnimation(notification: Notification) {
//guard let theView = notification.object as? NSView else { return }
let originalOrigin = self.view.frame.origin
// Perform the animation
NSAnimationContext.runAnimationGroup({ horizontalAnimation in
horizontalAnimation.duration = 0.05
// Animate to the new origin
self.view.animator().frame.origin.x += intensity // adding value moves the view right
}) { // Completion handler
self.view.frame.origin = originalOrigin
self.observatory.post(name: .moveUpAnimation, object: nil)
}
}
func moveUpAnimation(notification: Notification) {
//guard let theView = notification.object as? NSView else { return }
let originalOrigin = self.view.frame.origin
NSAnimationContext.runAnimationGroup({ verticalAnimation in
verticalAnimation.duration = 0.05
// Animate to the new angle
self.view.animator().frame.origin.y += intensity // adding value moves the view up
}) { // Completion handler
self.view.frame.origin = originalOrigin
self.observatory.post(name: .rotateRightAnimation, object: nil)
}
}
func rotateLeftAnimation(notification: Notification) {
// Prepare the anchor point to rotate around the center
let newAnchorPoint = CGPoint(x: 0.5, y: 0.5)
view.layer?.anchorPoint = newAnchorPoint
// Prevent the anchor point from modifying views location on screen
guard var position = view.layer?.position else { return }
position.x += view.bounds.maxX * newAnchorPoint.x
position.y += view.bounds.maxY * newAnchorPoint.y
view.layer?.position = position
// Configure the animation
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotateAnimation.byValue = intensity / 180 * CGFloat.pi
rotateAnimation.duration = 0.05;
let unRotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
unRotateAnimation.byValue = -(intensity / 180 * CGFloat.pi)
unRotateAnimation.duration = 0.05;
// Trigger the animation
CATransaction.begin()
view.layer?.add(rotateAnimation, forKey: "rotate")
CATransaction.setCompletionBlock {
// Handle animation completion
self.view.layer?.add(unRotateAnimation, forKey: "rotate")
self.observatory.post(name: .moveRightAnimation, object: nil)
}
CATransaction.commit()
}
func rotateRightAnimation(notification: Notification) {
// Prepare the anchor point to rotate around the center
let newAnchorPoint = CGPoint(x: 0.5, y: 0.5)
view.layer?.anchorPoint = newAnchorPoint
// Prevent the anchor point from modifying views location on screen
guard var position = view.layer?.position else { return }
position.x += view.bounds.maxX * newAnchorPoint.x
position.y += view.bounds.maxY * newAnchorPoint.y
view.layer?.position = position
// Configure the animation
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotateAnimation.byValue = -(intensity / 180 * CGFloat.pi)
rotateAnimation.duration = 0.05;
let unRotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
unRotateAnimation.byValue = intensity / 180 * CGFloat.pi
unRotateAnimation.duration = 0.05;
// Trigger the animation
CATransaction.begin()
view.layer?.add(rotateAnimation, forKey: "rotate")
CATransaction.setCompletionBlock {
// Handle animation completion
self.view.layer?.add(unRotateAnimation, forKey: "rotate")
self.observatory.post(name: .shakeView, object: nil)
}
CATransaction.commit()
}
}
extension Notification.Name {
static var shakeView: Notification.Name {
.init(rawValue: "ViewController.shakeView")
}
static var moveDownAnimation: Notification.Name {
.init(rawValue: "ViewController.moveDownAnimation")
}
static var moveLeftAnimation: Notification.Name {
.init(rawValue: "ViewController.moveLeftAnimation")
}
static var moveRightAnimation: Notification.Name {
.init(rawValue: "ViewController.moveRightAnimation")
}
static var moveUpAnimation: Notification.Name {
.init(rawValue: "ViewController.moveUpAnimation")
}
static var rotateLeftAnimation: Notification.Name {
.init(rawValue: "ViewController.rotateLeftAnimation")
}
static var rotateRightAnimation: Notification.Name {
.init(rawValue: "ViewController.rotateRightAnimation")
}
}

Navigation view transition full screen to a view with corner radius

I am trying to create an app home screen animation from splash, like after launch screen completed (full)screen color transforms into an app logo background color. Currently below code kind of archive what I expected. But, that transformation CAShapeLayer doesn't do with corner radius. Without corner radius it works as normal, when I try to use circle/oval/corner radius animation seems like below gif.
Tried few other StackOverflow answers which create circle animation those are not working. Here one of those.
weak var viewTransitionContext: UIViewControllerContextTransitioning!
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
viewTransitionContext = transitionContext
guard let fromVC = viewTransitionContext.viewController(forKey: .from) else { return }
guard let toVC = viewTransitionContext.viewController(forKey: .to) else { return }
if fromVC.isKind(of: SOGSplashViewController.self) && toVC.isKind(of: SOGHomeViewController.self) {
guard let toVCView = transitionContext.view(forKey: .to) else { return }
guard let fromVCView = transitionContext.view(forKey: .from) else { return }
let containerView = transitionContext.containerView
let labelWidth = UIDevice.width() * 0.75
let labelHeight = labelWidth * 0.7
let xAxis = (UIDevice.width() - labelWidth)/2.0
let yAxis = ((UIDevice.height()/2.0) - labelHeight)/2.0
let labelRect = CGRect(x: xAxis, y: yAxis, width: labelWidth, height: labelHeight)
let radius = (UIDevice.height()/2.0)*0.1
let fromFrame = fromVCView.bounds
let animationTime = transitionDuration(using: transitionContext)
let maskLayer = CAShapeLayer()
maskLayer.isOpaque = false
maskLayer.fillColor = fromVCView.backgroundColor?.cgColor
maskLayer.backgroundColor = UIColor.clear.cgColor
maskLayer.path = toPathValue.cgPath
let maskAnimationLayer = CABasicAnimation(keyPath: "path")
maskAnimationLayer.fromValue = (UIBezierPath(rect: fromFrame)).cgPath
maskAnimationLayer.toValue = toPathValue.cgPath
maskAnimationLayer.duration = animationTime
maskAnimationLayer.delegate = self as? CAAnimationDelegate
containerView.addSubview(fromVCView)
containerView.addSubview(toVCView)
fromVCView.layer.add(maskAnimationLayer, forKey: nil)
maskLayer.add(maskAnimationLayer, forKey: "path")
containerView.layer.addSublayer(maskLayer)
let deadLineTime = DispatchTime.now() + .seconds(1)
DispatchQueue.main.asyncAfter(deadline: deadLineTime) {
UIView.animate(withDuration: 0.2, animations: {
maskLayer.opacity = 0
}, completion: { (isSuccess) in
self.viewTransitionContext.completeTransition(true)
})
}
}
}
Transforming a rectangular path to a rounded rectangular path is a very complex operation if you do it through a generic way like Core Animation.. You should better use the cornerRadius property of CALayer which is animatable.
Here is a working example with a constraint based animation:
class ViewController: UIViewController {
#IBOutlet var constraints: [NSLayoutConstraint]!
#IBOutlet var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
self.contentView.layer.cornerRadius = 10.0
self.animate(nil)
}
#IBAction func animate(_ sender: Any?) {
for c in constraints {
c.constant = 40.0
}
UIView.animate(withDuration: 4.0) {
self.view.layoutIfNeeded()
self.contentView.layer.cornerRadius = 40.0
}
}
}
contentView points to the inner view which should be animated, and constraints refer to the four layout constraints defining the distances from the view controller's view to the content view.
This is just a simple, rough example, which can certainly be improved.

Using UIPercentDrivenInteractiveTransition with CABasicAnimation has weird glitch

I'm implementing custom transition using CABasicAnimation and UIView.animate both. Also need to implement a custom interactive transition using UIPercentDrivenInteractiveTransition which exactly copies the behavior of the native iOS swipe back. Animation without a back swipe gesture (when I'm pushing and popping by the back arrow) works fine and smoothly. Moreover, swipe back also works smoothly, except when the gesture velocity is more than 900
Gesture Recognition function:
#objc func handleBackGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
guard animationTransition != nil else { return }
switch gesture.state {
case .began:
interactionController = TransparentNavigationControllerTransitionInteractor(duration: anumationDuration)
popViewController(animated: true)
case .changed:
guard let view = gesture.view?.superview else { return }
let translation = gesture.translation(in: view)
var percentage = translation.x / view.bounds.size.width
percentage = min(1.0, max(0.0, percentage))
shouldCompleteTransition = percentage > 0.5
interactionController?.update(percentage)
case .cancelled, .failed, .possible:
if let interactionController = self.interactionController {
isInteractiveStarted = false
interactionController.cancel()
}
case .ended:
interactionController?.completionSpeed = 0.999
let greaterThanMaxVelocity = gesture.velocity(in: view).x > 800
let canFinish = shouldCompleteTransition || greaterThanMaxVelocity
canFinish ? interactionController?.finish() : interactionController?.cancel()
interactionController = nil
#unknown default: assertionFailure()
}
}
UIPercentDrivenInteractiveTransition class. Here I'm synchronizing layer animation.
final class TransparentNavigationControllerTransitionInteractor: UIPercentDrivenInteractiveTransition {
// MARK: - Private Properties
private var context: UIViewControllerContextTransitioning?
private var pausedTime: CFTimeInterval = 0
private let animationDuration: TimeInterval
// MARK: - Initialization
init(duration: TimeInterval) {
self.animationDuration = duration * 0.4 // I dk why but layer duration should be less
super.init()
}
// MARK: - Public Methods
override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
context = transitionContext
pausedTime = transitionContext.containerView.layer.convertTime(CACurrentMediaTime(), from: nil)
transitionContext.containerView.layer.speed = 0
transitionContext.containerView.layer.timeOffset = pausedTime
}
override func finish() {
restart(isFinishing: true)
super.finish()
}
override func cancel() {
restart(isFinishing: false)
super.cancel()
}
override func update(_ percentComplete: CGFloat) {
super.update(percentComplete)
guard let transitionContext = context else { return }
let progress = CGFloat(animationDuration) * percentComplete
transitionContext.containerView.layer.timeOffset = pausedTime + Double(progress)
}
// MARK: - Private Methods
private func restart(isFinishing: Bool) {
guard let transitionLayer = context?.containerView.layer else { return }
transitionLayer.beginTime = transitionLayer.convertTime(CACurrentMediaTime(), from: nil)
transitionLayer.speed = isFinishing ? 1 : -1
}
}
And here is my Dismissal animation function in UIViewControllerAnimatedTransitioning class
private func runDismissAnimationFrom(
_ fromView: UIView,
to toView: UIView,
in transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to) else { return }
toView.frame = toView.frame.offsetBy(dx: -fromView.frame.width / 3, dy: 0)
let toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
let fromViewFinalFrame = fromView.frame.offsetBy(dx: fromView.frame.width, dy: 0)
// Create mask to hide bottom view with sliding
let slidingMask = CAShapeLayer()
let initialMaskPath = UIBezierPath(rect: CGRect(
x: fromView.frame.width / 3,
y: 0,
width: 0,
height: toView.frame.height)
)
let finalMaskPath = UIBezierPath(rect: toViewFinalFrame)
slidingMask.path = initialMaskPath.cgPath
toView.layer.mask = slidingMask
toView.alpha = 0
let slidingAnimation = CABasicAnimation(keyPath: "path")
slidingAnimation.fromValue = initialMaskPath.cgPath
slidingAnimation.toValue = finalMaskPath.cgPath
slidingAnimation.timingFunction = .init(name: .linear)
slidingMask.path = finalMaskPath.cgPath
slidingMask.add(slidingAnimation, forKey: slidingAnimation.keyPath)
UIView.animate(
withDuration: duration,
delay: 0,
options: animationOptions,
animations: {
fromView.frame = fromViewFinalFrame
toView.frame = toViewFinalFrame
toView.alpha = 1
},
completion: { _ in
toView.layer.mask = nil
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
I note that glitch occurs only when a swipe has a grand velocity.
Here a video with the result of smooth animation at normal speed and not smooth at high speed - https://youtu.be/1d-kTPlhNvE
UPD:
I've already tried to use UIViewPropertyAnimator combine with
interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating
But the result is another type of glitching.
I've solved the issue, just change a part of restart function:
transitionLayer.beginTime =
transitionLayer.convertTime(CACurrentMediaTime(), from: nil) - transitionLayer.timeOffset
transitionLayer.speed = 1
I don't really understand why, but looks like timeOffset subtraction works!

Smooth object rotation in ARSCNView

I'm trying to make my pan gesture to be as smooth as the jigspace app when rotating 3d objects in AR. Here's what I have right now:
#objc func rotateObject(sender: UIPanGestureRecognizer) {
let sceneView = sender.view as! ARSCNView
var currentAngleY: Float = 0.0
let translation = sender.translation(in: sceneView)
var newAngleY = Float(translation.x)*Float(Double.pi)/180
sceneView.scene.rootNode.enumerateChildNodes { (node, stop) in
if sender.state == .changed {
newAngleY -= currentAngleY
node.eulerAngles.y = newAngleY
} else if sender.state == .ended {
currentAngleY = newAngleY
node.removeAllActions()
}
}
}
There seems to be a delay when I'm using it and I'm trying to figure out how to make the rotation as smooth as possible, again, kinda like jigspace or the Ikea app.
I've also noticed that when I try to rotate the object when it's in a certain angle, it could get quite awkward.
Looking at your rotate object function it seems like some of the logic is not quite right.
Firstly, I believe that the var currentAngleY: Float = 0 should be outside of your function body.
Secondly you should be adding the currentAngleY to the newAngleY variable e.g:
/// Rotates The Models On Their YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateModels(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
DispatchQueue.main.async {
self.sceneView.scene.rootNode.enumerateChildNodes { (node, _) in
node.eulerAngles.y = newAngleY
}
}
if(gesture.state == .ended) { currentAngleY = newAngleY }
}
An example therefore of this in a working context would be like so:
class ViewController: UIViewController {
#IBOutlet var augmentedRealityView: ARSCNView!
var currentAngleY: Float = 0
//-----------------------
// MARK: - View LifeCycle
//-----------------------
override func viewDidLoad() {
super.viewDidLoad()
//1. Generate Our Three Box Nodes
generateBoxNodes()
//2. Create Our Rotation Gesture
let rotateGesture = UIPanGestureRecognizer(target: self, action: #selector(rotateModels(_:)))
self.view.addGestureRecognizer(rotateGesture)
//3. Run The Session
let configuration = ARWorldTrackingConfiguration()
augmentedRealityView.session.run(configuration)
}
//------------------------
// MARK: - Node Generation
//------------------------
/// Generates Three SCNNodes With An SCNBox Geometry
func generateBoxNodes(){
//1. Create An Array Of Colours For Each Face
let colours: [UIColor] = [.red, .green, .blue, .purple, .cyan, .black]
//2. Create An SCNNode Wih An SCNBox Geometry
let boxNode = SCNNode()
let boxGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.01)
boxNode.geometry = boxGeometry
//3. Create A Different Material For Each Face
var materials = [SCNMaterial]()
for i in 0..<5{
let faceMaterial = SCNMaterial()
faceMaterial.diffuse.contents = colours[i]
materials.append(faceMaterial)
}
//4. Set The Geometries Materials
boxNode.geometry?.materials = materials
//5. Create Two More Nodes By Cloning The First One
let secondBox = boxNode.flattenedClone()
let thirdBox = boxNode.flattenedClone()
//6. Position Them In A Line & Add To The Scene
boxNode.position = SCNVector3(-0.2, 0, -1.5)
secondBox.position = SCNVector3(0, 0, -1.5)
thirdBox.position = SCNVector3(0.2, 0, -1.5)
self.augmentedRealityView.scene.rootNode.addChildNode(boxNode)
self.augmentedRealityView.scene.rootNode.addChildNode(secondBox)
self.augmentedRealityView.scene.rootNode.addChildNode(thirdBox)
}
//----------------------
// MARK: - Node Rotation
//----------------------
/// Rotates The Models On Their YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateModels(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
DispatchQueue.main.async {
self.augmentedRealityView.scene.rootNode.enumerateChildNodes { (node, _) in
node.eulerAngles.y = newAngleY
}
}
if(gesture.state == .ended) { currentAngleY = newAngleY }
}
}
Hope it helps...

How do I detect which way the user is swiping?

I'm using the code below so that the user can swipe a UIImageView to whatever direction they want. I need to detect which way the user is swiping. If it's swiped for example to the left it needs to println "Swiped left!" Is there any way to do this?
let ThrowingThreshold: CGFloat = 1000
let ThrowingVelocityPadding: CGFloat = 35
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var redSquare: UIView!
#IBOutlet weak var blueSquare: UIView!
private var originalBounds = CGRect.zeroRect
private var originalCenter = CGPoint.zeroPoint
private var animator: UIDynamicAnimator!
private var attachmentBehavior: UIAttachmentBehavior!
private var pushBehavior: UIPushBehavior!
private var itemBehavior: UIDynamicItemBehavior!
func resetDemo() {
animator.removeAllBehaviors()
UIView.animateWithDuration(0.45) {
self.imageView.bounds = self.originalBounds
self.imageView.center = self.originalCenter
self.imageView.transform = CGAffineTransformIdentity
}
}
#IBAction func handleAttachmentGesture(sender: UIPanGestureRecognizer) {
let location = sender.locationInView(self.view)
let boxLocation = sender.locationInView(self.imageView)
switch sender.state {
case .Began:
println("Your touch start position is \(location)")
println("Start location in image is \(boxLocation)")
// 1
animator.removeAllBehaviors()
// 2
let centerOffset = UIOffset(horizontal: boxLocation.x - imageView.bounds.midX,
vertical: boxLocation.y - imageView.bounds.midY)
attachmentBehavior = UIAttachmentBehavior(item: imageView,
offsetFromCenter: centerOffset, attachedToAnchor: location)
// 3
redSquare.center = attachmentBehavior.anchorPoint
blueSquare.center = location
// 4
animator.addBehavior(attachmentBehavior)
case .Ended:
println("Your touch end position is \(location)")
println("End location in image is \(boxLocation)")
animator.removeAllBehaviors()
// 1
let velocity = sender.velocityInView(view)
let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
if magnitude > ThrowingThreshold {
// 2
let pushBehavior = UIPushBehavior(items: [imageView], mode: .Instantaneous)
pushBehavior.pushDirection = CGVector(dx: velocity.x / 10, dy: velocity.y / 10)
pushBehavior.magnitude = magnitude / ThrowingVelocityPadding
self.pushBehavior = pushBehavior
animator.addBehavior(pushBehavior)
// 3
let angle = Int(arc4random_uniform(20)) - 10
itemBehavior = UIDynamicItemBehavior(items: [imageView])
itemBehavior.friction = 0.2
itemBehavior.allowsRotation = true
itemBehavior.addAngularVelocity(CGFloat(angle), forItem: imageView)
animator.addBehavior(itemBehavior)
// 4
let timeOffset = Int64(0.4 * Double(NSEC_PER_SEC))
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeOffset), dispatch_get_main_queue()) {
self.resetDemo()
}
} else {
resetDemo()
}
default:
attachmentBehavior.anchorPoint = sender.locationInView(view)
redSquare.center = attachmentBehavior.anchorPoint
}
override func viewDidLoad() {
super.viewDidLoad()
animator = UIDynamicAnimator(referenceView: view)
originalBounds = imageView.bounds
originalCenter = imageView.center
You can use the translationInView function of the gesture to get a CGPoint representing the movement of the gesture since it started (or since you last reset it). This tells you the relative movement in the x and y directions.
If you're interested in the overall movement then you can just read this and use the sign of the x and y values to determine the direction moved.
If you're interested in the instantaneous direction of movement then you can use the same approach, but after reading the translation you can use setTranslation:inView: to reset to CGPointZero. In this way the translation provided in the next callback is the delta movement.