Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spin bottle with UIGestureRecognizer

I am using this code now to spin bottle on button tap:

@IBAction func spinButton(sender: AnyObject) {
        let rotateView = CABasicAnimation()
        let randonAngle = arc4random_uniform(360) + 720
        rotateView.fromValue = 0
        rotateView.toValue = Float(randonAngle) * Float(M_PI) / 180.0
        rotateView.duration = 3
        rotateView.delegate = self
        rotateView.repeatCount = 0
        rotateView.removedOnCompletion = false
        rotateView.fillMode = kCAFillModeForwards
        rotateView.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        bottleImageView.layer.addAnimation(rotateView, forKey: "transform.rotation.z")
    }

But how can I rotate the button using gesture? So the harder/faster I move my finger, the faster the bottle will spin

like image 739
Roduck Nickes Avatar asked Jun 22 '16 12:06

Roduck Nickes


3 Answers

The simple answer to this is... use a UIScrollView.

From my question here... Loop UIScrollView but continue decelerating

Translating it to Swift is trivial but here goes...

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    //make the content size really big so that the targetOffset of the deceleration will never be met.

    scrollView.contentSize = CGSize(width: CGRectGetWidth(scrollView.frame) * 100.0, height: CGRectGetHeight(scrollView.frame))
    //set the contentOffset of the scroll view to a point in the center of the contentSize.
    scrollView.setContentOffset(CGPoint(CGRectGetWidth(scrollView.frame) * 50, 0), animated: false)
}

func rotateImageView() {
    //Calculate the percentage of one "frame" that is the current offset.

    // each percentage of a "frame" equates to a percentage of 2 PI Rads to rotate
    let minOffset = CGRectGetWidth(scrollView.frame) * 50.0
    let maxOffset = CGRectGetWidth(scrollView.frame) * 51.0

    let offsetDiff = maxOffset - minOffset

    let currentOffset = scrollView.contentOffset.x - minOffset

    let percentage = currentOffset / offsetDiff

    arrowView.transform = CGAffineTransformMakeRotation(M_PI * 2 * percentage)
}

func scrollViewDidScroll(scrollView: UIScrollView) {
    //the scrollView moved so update the rotation of the image
    rotateImageView()
}

func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    //the scrollview stopped moving.
    //set the content offset back to be in the middle
    //but make sure to keep the percentage (used above) the same
    //this ensures the arrow is pointing in the same direction as it ended decelerating

    let diffOffset = scrollView.contentOffset.x

    while diffOffset >= CGRectGetWidth(scrollView.frame) {
        diffOffset = diffOffset - CGRectGetWidth(scrollView.frame)
    }

    scrollView.setContentOffset(CGPoint(x: CGRectGetWidth(scrollView.frame) * 50 + diffOffset, y: 0), animated:false)
}

In this example my spinning view is the arrowView. The scrollView and arrowView should both be subviews of the view controller's view. The scrollView should not have anything in it.

N.B. This is done in the browser so there may be some syntax problems. You may also have to cast some of the numbers to CGFloat etc...

Also, being able to translate from Objective-C is essential for any iOS dev. Millions of apps are written using Objective-C. The syntax may be slightly different but all of the APIs are the same.

Learning how to do this is something that every iOS dev should be doing.

like image 143
Fogmeister Avatar answered Nov 04 '22 10:11

Fogmeister


I have managed to create a sample app that has a view in the center which you can spin with UIRotationGestureRecognizer and spin speed will be affected based on rotation speed. I've used UIDynamics for this:

class ViewController: UIViewController {

@IBOutlet weak var sampleView: UIView!

var animator: UIDynamicAnimator?

override func viewDidLoad() {
    super.viewDidLoad()

    setupRotationGesture()

}

override func viewDidAppear(animated: Bool) {

    animator = UIDynamicAnimator(referenceView: self.view)

    let sampleViewBehavior = UIDynamicItemBehavior(items: [self.sampleView])
    sampleViewBehavior.allowsRotation = true // view can rotate
    animator?.addBehavior(sampleViewBehavior)

    let anchoredSuperViewBehavior = UIDynamicItemBehavior(items: [self.view])
    anchoredSuperViewBehavior.anchored = true
    animator?.addBehavior(anchoredSuperViewBehavior)

    // Attachment between the sample view and super view at center anchor point.

    let attachment = UIAttachmentBehavior.pinAttachmentWithItem(self.sampleView, attachedToItem: self.view, attachmentAnchor: CGPointMake(self.view.center.x + 1, self.view.center.y + 1))
    animator?.addBehavior(attachment)
}


// MARK: - Rotation Gesture -

func setupRotationGesture(){

    let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotationGesture(_:)))
    self.sampleView.addGestureRecognizer(rotationGesture)
}

func handleRotationGesture(gesture: UIRotationGestureRecognizer){

    if gesture.state == .Ended {

        let push = UIPushBehavior(items: [self.sampleView], mode: .Instantaneous)

        push.magnitude = abs(50.5 * gesture.velocity)
        let transform = self.sampleView.transform
        push.angle = atan2(transform.b, transform.a);
        animator?.addBehavior(push)
    }

}
 }

When you run you will be able to spin the view based on speed of rotation gesture.

like image 45
Hossam Ghareeb Avatar answered Nov 04 '22 11:11

Hossam Ghareeb


Use UIPanGestureRecognizer. Create a panGestureRecognizer and add it to your bottle's superview and implement it's delegate. It has variables such as velocityInView, translationInView. When user finishes panning, Use them to calculate the angle, speed and duration of your spin.

like image 1
farzadshbfn Avatar answered Nov 04 '22 09:11

farzadshbfn