swift - 实现命运之轮

标签 swift cakeyframeanimation

我正在实现一个命运之轮,灵感来自 stackoverflow 中的其他帖子,至少它在旋转。目前我的问题是,如果我向下滑动图像的右侧,轮子会朝错误的方向旋转。

有人能看出哪里出了问题吗?

class WOFView: UIView {

@IBOutlet weak var wheelImage: UIImageView!
private var history = [Dictionary<String, Any>]()
private var rotation: CGFloat = 0
private var startAngle: CGFloat = 0
private var circleRotationOffset: CGFloat = 0


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

    if let touchPoint = touches.first?.location(in: self){
        startAngle = atan2(self.frame.width - touchPoint.y, self.frame.height - touchPoint.x)
        rotation = startAngle
        history.removeAll()
    }
}


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

    guard let touchPoint = touches.first?.location(in: self) else {
        return
    }

    let dic = ["time" : NSNumber(value: CFAbsoluteTimeGetCurrent()),
               "point": NSValue(cgPoint: touchPoint),
               "rotation": NSNumber(value: Float(circleRotationOffset + rotation))]

    history.insert(dic, at: 0)
    if history.count == 3{
        history.removeLast()
    }

    rotation = atan2(self.frame.width - touchPoint.y, self.frame.height - touchPoint.x) - startAngle
    wheelImage.transform = CGAffineTransform(rotationAngle: circleRotationOffset + rotation)
}


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

    guard let touchPoint = touches.first?.location(in: self) else {
        return
    }

    guard let lastObject = history.last else{
        return
    }

    guard let pointValue = lastObject["point"] as? CGPoint else{
        return
    }

    guard let timeValue = lastObject["time"] as? NSNumber else {
        return
    }

    guard let rotationValue = lastObject["rotation"] as? NSNumber else {
        return
    }

    let timeDif = CFAbsoluteTimeGetCurrent() - (timeValue.doubleValue)
    circleRotationOffset = circleRotationOffset + rotation
    let lastRotation = rotationValue.floatValue

    let dist = sqrt(((pointValue.x - touchPoint.x) * (pointValue.x - touchPoint.x)) +
        ((pointValue.y - touchPoint.y) * (pointValue.y - touchPoint.y)))

    let strength = max(Double(min(1.0, dist / 80.0)) * (timeDif / 0.25) * M_PI * 2, 0.3) * 30
    print("S: \(strength)")

    let p = circleRotationOffset
    let dif = circleRotationOffset - CGFloat(lastRotation)
    var inc = dif > 0

    if dif > 3 || dif < -3{
        inc = !inc
    }


    if (inc){
        circleRotationOffset += CGFloat(strength)
    }else{
        circleRotationOffset -= CGFloat(strength)
    }

    let anim = CAKeyframeAnimation(keyPath: "transform.rotation.z")
    anim.duration = max(strength / 2, 1.0)
    anim.isCumulative = true
    anim.values = [NSNumber(value: Float(p)), Float(circleRotationOffset)]
    anim.keyTimes = [NSNumber(value: Float(0)),NSNumber(value: Float(1.0))]
    anim.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]
    anim.isRemovedOnCompletion = false
    anim.fillMode = kCAFillModeForwards

    wheelImage.layer.removeAllAnimations()
    wheelImage.layer.add(anim, forKey: "rotate")
}

编辑 我使用 UIPanGestureRecognizer 简化了事情并想分享结果:

   enum SpinningDirection{

        case clockwise
        case antiClockwise
    }

    enum MajorDirection{
        case up
        case down
        case left
        case right
    }

    enum Quadrant{
        case ul
        case ur
        case ll
        case lr
    }

    class WOFView: UIView, CAAnimationDelegate {

        @IBOutlet weak var wheelImage: UIImageView!

        private var maxSpeed = 0
        private var majorDirection = MajorDirection.right
        private var quadrant =  Quadrant.ul
        private var spinningDirection = SpinningDirection.clockwise
        private var winner = ""

        func  setup(){

            let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.respondToPanGesture))
            addGestureRecognizer(panGesture)
        }


        func respondToPanGesture(gesture: UIPanGestureRecognizer){

            let velocity = gesture.velocity(in: self)

            let vX = abs(velocity.x)
            let vY = abs(velocity.y)
            let speed = Int(vX + vY)

            if speed > maxSpeed{
                maxSpeed = speed
            }

            let location = gesture.location(in: self)

            if vX > vY{
                majorDirection = (velocity.x > 0) ? .right : .left
            }
            else{
                majorDirection = (velocity.y > 0) ? .down : .up
            }

            if location.x < self.frame.width / 2 {
                quadrant = (location.y < self.frame.height / 2 ) ? .ul : .ll
            }
            else {
               quadrant = (location.y < self.frame.height / 2 ) ? .ur : .lr
            }

            switch quadrant {
            case .ul:

                switch majorDirection {
                case .down, .left:
                    spinningDirection = .antiClockwise
                case .up, .right:
                    spinningDirection = .clockwise
                }

            case .ur:

                switch majorDirection {
                case .down, .right:
                    spinningDirection = .clockwise
                case .up, .left:
                    spinningDirection = .antiClockwise
                }

            case .lr:

                switch majorDirection {
                case .down, .left:
                    spinningDirection = .clockwise
                case .up, .right:
                    spinningDirection = .antiClockwise
                }

            case .ll:
                switch majorDirection {
                case .down, .right:
                    spinningDirection = .antiClockwise
                case .up, .left:
                    spinningDirection = .clockwise
                }
            }



            if gesture.state == .began{
                maxSpeed = 0
                self.isUserInteractionEnabled = false
            }

            if gesture.state == .ended{
                print("Ended")

                print("MaxSpeed: \(maxSpeed)")
                print("direction: \(spinningDirection)")

                startAnimation(speed: maxSpeed, direction: spinningDirection)
            }
        }


        private func startAnimation(speed: Int, direction : SpinningDirection){

            var duration = Double(speed) / 10
            if duration > 10{
                duration = 10
            }
            if duration < 3{
                duration = 3
            }

            print("duration: \(duration)")

            let multiplier = (direction == .clockwise) ? -1.0 : 1.0
            let normalizedSpeed = Double(speed) / 10 * multiplier

            let goal = Double((speed * 100) % Int(2 * Double.pi * 100)) / 100.0
            print("goal: \(goal)")

            let halfPi = Double.pi/2
            switch goal {

            case 0*halfPi...1*halfPi:
                winner = "1"
            case 1*halfPi...2*halfPi:
                winner = "4"
            case 2*halfPi...3*halfPi:
                winner = "3"
            case 3*halfPi...4*halfPi:
                winner = "2"

            default:
                print("?")
            }

            let anim = CAKeyframeAnimation(keyPath: "transform.rotation.z")
            anim.duration = duration
            anim.isCumulative = true
            anim.values = [NSNumber(value: Float(normalizedSpeed)), Float(goal)]
            anim.keyTimes = [NSNumber(value: Float(0)),NSNumber(value: Float(1))]
            anim.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]
            anim.isRemovedOnCompletion = false
            anim.fillMode = kCAFillModeForwards
            anim.delegate = self
            wheelImage.layer.removeAllAnimations()
            wheelImage.layer.add(anim, forKey: "rotate")
        }


        func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
            print("The winner is \(winner)")
            self.isUserInteractionEnabled = true   
        }
    }

最佳答案

介绍一种找出哪一面被触摸的方法:

func touch(_ touch:UITouch, isInLeftHalfOf view: UIView) -> Bool {
    let positionInView = touch.location(in: view)

    return positionInView.x < view.frame.midX
}

如果不是左侧,则反转旋转:

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    // your code ...

    rotation = atan2(self.frame.width - touchPoint.y, self.frame.height - touchPoint.x) - startAngle

    if !touch(touches.first!, isInLeftHalfOf: wheelImage) {
        rotation = -rotation
    }
    wheelImage.transform = CGAffineTransform(rotationAngle: circleRotationOffset + rotation)
}

即使这解决了您问题中提到的情况(向下滑动图像的右侧),您很可能至少需要一些微调,但您明白了。

结果:

关于swift - 实现命运之轮,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41616582/

相关文章:

swift - 如何将 Swift 协议(protocol)与泛型方法和泛型类型一起使用

ios - 快速判断可选值不起作用

ios - CAKeyframeAnimation 在 UIImageView 中具有不同的动画持续时间值

css - 如何根据第 n 个位置添加*渐进式*动画延迟?

swift - 对 UIView 扩展的文本颜色进行动画处理

ios - 如何将 JSON 值从一个 ViewController 传递到另一个

ios - for 循环 CGFloat 出错

ios - UIButton 和 UITextField 不响应作为 UIViewController 的一部分

swift - 带 cornerRadius 的矩形中的线动画

ios - CAKeyframeAnimation - 沿路径移动并使用缓动