Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
423 views
in Technique[技术] by (71.8m points)

ios - Make rotation with one finger in Swift 4

I created a UIGestureRecognizer to rotate a view with only one finger.

The view rotate at the beginning but as soon as it reached a certain degree the rotation rotate in the other direction.

Can you help me with my code?

UIViewcontroller <- Everything is fine here

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.

    let wheel = TestWheelView()
    wheel.frame = CGRect.init(x: self.view.center.x - 120, y: self.view.center.y - 120, width: 240, height: 240)
    self.view.addSubview(wheel)
    wheel.addGestureRecognizer(TestRotateGestureRecognizer())
}

UIGestureRecognizer <- The problem is here

import UIKit

class TestRotateGestureRecognizer: UIGestureRecognizer {
    var previousPoint = CGPoint()
    var currentPoint = CGPoint()
    var startAngle = CGFloat()
    var currentAngle = CGFloat()
    var currentRotation = CGFloat()
    var totalRotation = CGFloat()

    func angleForPoint(_ point:CGPoint) -> CGFloat{
        var angle = atan2(point.y - (self.view?.center.y)!, point.x - (self.view?.center.x)!)

        return angle
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)

        if let firstTouch = touches.first {
            previousPoint = firstTouch.previousLocation(in: self.view)
            currentPoint = firstTouch.location(in: self.view)
            startAngle = angleForPoint(previousPoint)
            currentAngle = angleForPoint(currentPoint)

            currentRotation = currentAngle - startAngle
            totalRotation += currentRotation

            self.view?.transform = CGAffineTransform(rotationAngle: totalRotation)
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesEnded(touches, with: event)
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It is more a math question than a Swift question. Regarding the math part you need to check if the result of your method that calculates the rotation is negative and return the absolute value, otherwise return 360 minus the angle:

 func angle(from location: CGPoint) -> CGFloat {
    let deltaY = location.y - view.center.y
    let deltaX = location.x - view.center.x
    let angle = atan2(deltaY, deltaX) * 180 / .pi
    return angle < 0 ? abs(angle) : 360 - angle
}

Regarding the animation part I suggest using CABasicAnimation, setting isRemovedOnCompletion to false, fillMode to kCAFillModeForwards and timingFunction to linear. Other important setting is the from and to which you should use the same value for both of them with a duration of 0:

fileprivate let rotateAnimation = CABasicAnimation()
func rotate(to: CGFloat, duration: Double = 0) {
    rotateAnimation.fromValue = to
    rotateAnimation.toValue = to
    rotateAnimation.duration = duration
    rotateAnimation.repeatCount = 0
    rotateAnimation.isRemovedOnCompletion = false
    rotateAnimation.fillMode = kCAFillModeForwards
    rotateAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    imageView.layer.add(rotateAnimation, forKey: "transform.rotation.z")
}

To convert from degrees to radians you can use the extension from this answer:

extension FloatingPoint {
    var degreesToRadians: Self { return self * .pi / 180 }
    var radiansToDegrees: Self { return self * 180 / .pi }
}

To preserve the rotation between launches you can add a computed property to UserDefault:

extension UserDefaults {
    /// rotation persistant computed property
    var rotation: CGFloat {
        get {
            return CGFloat(double(forKey: "rotation"))
        }
        set {
            set(Double(newValue), forKey: "rotation")
        }
    }
}

Regarding the gesture recognizer you need to switch the gesture state to detect the begin, change and end of it and act accordingly:

fileprivate var rotation: CGFloat = UserDefaults.standard.rotation
fileprivate var startRotationAngle: CGFloat = 0
@objc func pan(_ gesture: UIPanGestureRecognizer) {
    let location = gesture.location(in: view)
    let gestureRotation = CGFloat(angle(from: location)) - startRotationAngle
    switch gesture.state {
    case .began:
        // set the start angle of rotation 
        startRotationAngle = angle(from: location)
    case .changed:
        rotate(to: rotation - gestureRotation.degreesToRadians)
    case .ended:
        // update the amount of rotation
        rotation -= gestureRotation.degreesToRadians
    default :
        break
    }
    // save the final position of the rotation to defaults
    UserDefaults.standard.rotation = rotation
}

And add the gesture recognizer to your view:

class ViewController: UIViewController, UIGestureRecognizerDelegate {
    @IBOutlet weak var imageView: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()
        let address = "https://i.stack.imgur.com/xnZXF.jpg"
        let url = URL(string: address)!
        rotate(to: rotation)
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else { return }
            DispatchQueue.main.async {
                self.imageView.image = UIImage(data: data)
                let pan = UIPanGestureRecognizer(target: self, action:#selector(self.pan))
                pan.minimumNumberOfTouches = 1
                pan.maximumNumberOfTouches = 1
                pan.delegate = self
                self.view.addGestureRecognizer(pan)
            }
        }.resume()
    }
    // rest of the view controller code
}

Sample project


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...