3

I'm implementing a wheel of fortune inspired by other post in stackoverflow and at least it is spinning. Currently my problem is that if I swipe on the right side of the image in the down direction the wheel spins in the wrong direction.

Can someone see what's wrong?

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")
}

}

EDIT I simplified things with a UIPanGestureRecognizer and want to share the results:

   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   
        }
    }
netshark1000
  • 7,245
  • 9
  • 59
  • 116

2 Answers2

1

Introduce a method to find out which side was touched:

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

    return positionInView.x < view.frame.midX
}

and invert the rotation if it is not the left side:

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)
}

Even this fixes the situation mentioned in your question (swipe on the right side of the image in the down direction), you most probably need at least some fine tuning, but you get the idea.

Result:

shallowThought
  • 19,212
  • 9
  • 65
  • 112
  • good idea - thank you, but it doesnt really work as expected. Sometimes it jumps in the wrong direction depening on where I start or stop. Is it possible to let it follow my finger? – netshark1000 Jan 13 '17 at 10:31
  • That is what I meant with fine tuning. That is up to you :-). You can certainly track the fingers move as you get all touch changes. I would first try if simple gestures maybe fulfil your requirements (4 x `UISwipeGestureRecognizer`, one for each direction). – shallowThought Jan 13 '17 at 12:28
  • Thanks! Is this applied to my example above or have you implemented further tweaks? Does your example allow swiping from 7 to 4 on a clock? – netshark1000 Jan 13 '17 at 12:40
  • It is exactly your example plus the changes in my answer. No invisible tweaks. As said, you will at least need fine tuning. Hint: For 7-4 o'clock, you will have to also take swipe direction into account. Maybe another approach is easier. – shallowThought Jan 13 '17 at 15:26
-2

    var padding = {top:20, right:40, bottom:0, left:0},
            w = 500 - padding.left - padding.right,
            h = 500 - padding.top  - padding.bottom,
            r = Math.min(w, h)/2,
            rotation = 0,
            oldrotation = 0,
            picked = 100000,
            oldpick = [],
            color = d3.scale.category20();//category20c()
            //randomNumbers = getRandomNumbers();

        //http://osric.com/bingo-card-generator/?title=HTML+and+CSS+BINGO!&words=padding%2Cfont-family%2Ccolor%2Cfont-weight%2Cfont-size%2Cbackground-color%2Cnesting%2Cbottom%2Csans-serif%2Cperiod%2Cpound+sign%2C%EF%B9%A4body%EF%B9%A5%2C%EF%B9%A4ul%EF%B9%A5%2C%EF%B9%A4h1%EF%B9%A5%2Cmargin%2C%3C++%3E%2C{+}%2C%EF%B9%A4p%EF%B9%A5%2C%EF%B9%A4!DOCTYPE+html%EF%B9%A5%2C%EF%B9%A4head%EF%B9%A5%2Ccolon%2C%EF%B9%A4style%EF%B9%A5%2C.html%2CHTML%2CCSS%2CJavaScript%2Cborder&freespace=true&freespaceValue=Web+Design+Master&freespaceRandom=false&width=5&height=5&number=35#results

        var data = [
                    {"label":"Question 1",  "value":1,  "question":"What CSS property is used for specifying the area between the content and its border?"}, // padding
                    {"label":"Question 2",  "value":1,  "question":"What CSS property is used for changing the font?"}, //font-family
                    {"label":"Question 3",  "value":1,  "question":"What CSS property is used for changing the color of text?"}, //color
                    {"label":"Question 4",  "value":1,  "question":"What CSS property is used for changing the boldness of text?"}, //font-weight
                    {"label":"Question 5",  "value":1,  "question":"What CSS property is used for changing the size of text?"}, //font-size
                    {"label":"Question 6",  "value":1,  "question":"What CSS property is used for changing the background color of a box?"}, //background-color
                    {"label":"Question 7",  "value":1,  "question":"Which word is used for specifying an HTML tag that is inside another tag?"}, //nesting
                    {"label":"Question 8",  "value":1,  "question":"Which side of the box is the third number in: margin:1px 1px 1px 1px; ?"}, //bottom
                    {"label":"Question 9",  "value":1,  "question":"What are the fonts that don't have serifs at the ends of letters called?"}, //sans-serif
                    {"label":"Question 10", "value":1, "question":"With CSS selectors, what character prefix should one use to specify a class?"}, //period
                    {"label":"Question 11", "value":1, "question":"With CSS selectors, what character prefix should one use to specify an ID?"}, //pound sign
                    {"label":"Question 12", "value":1, "question":"In an HTML document, which tag holds all of the content people see?"}, //<body>
                    {"label":"Question 13", "value":1, "question":"In an HTML document, which tag indicates an unordered list?"}, //<ul>
                    {"label":"Question 14", "value":1, "question":"In an HTML document, which tag indicates the most important heading of your document?"}, //<h1>
                    {"label":"Question 15", "value":1, "question":"What CSS property is used for specifying the area outside a box?"}, //margin
                    {"label":"Question 16", "value":1, "question":"What type of bracket is used for HTML tags?"}, //< >
                    {"label":"Question 17", "value":1, "question":"What type of bracket is used for CSS rules?"}, // { }
                    {"label":"Question 18", "value":1, "question":"Which HTML tag is used for specifying a paragraph?"}, //<p>
                    {"label":"Question 19", "value":1, "question":"What should always be the very first line of code in your HTML?"}, //<!DOCTYPE html>
                    {"label":"Question 20", "value":1, "question":"What HTML tag holds all of the metadata tags for your page?"}, //<head>
                    {"label":"Question 21", "value":1, "question":"In CSS, what character separates a property from a value?"}, // colon
                    {"label":"Question 22", "value":1, "question":"What HTML tag holds all of your CSS code?"}, // <style>
                    {"label":"Question 23", "value":1, "question":"What file extension should you use for your web pages?"}, // .html
                    {"label":"Question 24", "value":1, "question":"Which coding language is used for marking up content and structure on a web page?"}, // HTML
                    {"label":"Question 25", "value":1, "question":"Which coding language is used for specifying the design of a web page?"}, // CSS
                    {"label":"Question 26", "value":1, "question":"Which coding language is used for adding functionality to a web page?"}, // JavaScript
                    {"label":"Question 27", "value":1, "question":"What CSS property is used for making the edges of a box visible?"}, // border
                    {"label":"Question 28", "value":1, "question":"What character symbol is used at the end of each CSS statement?"},//semi-colon
                    {"label":"Question 29", "value":1, "question":"By default, how wide is a <div> box?"}, //100%
                    {"label":"Question 30", "value":1, "question":"What character symbol do I use to specify multiple CSS selectors in one code block?"} //comma
        ];


        var svg = d3.select('#chart')
            .append("svg")
            .data([data])
            .attr("width",  w + padding.left + padding.right)
            .attr("height", h + padding.top + padding.bottom);

        var container = svg.append("g")
            .attr("class", "chartholder")
            .attr("transform", "translate(" + (w/2 + padding.left) + "," + (h/2 + padding.top) + ")");

        var vis = container
            .append("g");
            
        var pie = d3.layout.pie().sort(null).value(function(d){return 1;});

        // declare an arc generator function
        var arc = d3.svg.arc().outerRadius(r);

        // select paths, use arc generator to draw
        var arcs = vis.selectAll("g.slice")
            .data(pie)
            .enter()
            .append("g")
            .attr("class", "slice");
            

        arcs.append("path")
            .attr("fill", function(d, i){ return color(i); })
            .attr("d", function (d) { return arc(d); });

        // add the text
        arcs.append("text").attr("transform", function(d){
                d.innerRadius = 0;
                d.outerRadius = r;
                d.angle = (d.startAngle + d.endAngle)/2;
                return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")translate(" + (d.outerRadius -10) +")";
            })
            .attr("text-anchor", "end")
            .text( function(d, i) {
                return data[i].label;
            });

        container.on("click", spin);


        function spin(d){
            
            container.on("click", null);

            //all slices have been seen, all done
            console.log("OldPick: " + oldpick.length, "Data length: " + data.length);
            if(oldpick.length == data.length){
                console.log("done");
                container.on("click", null);
                return;
            }

            var  ps       = 360/data.length,
                 pieslice = Math.round(1440/data.length),
                 rng      = Math.floor((Math.random() * 1440) + 360);
                
            rotation = (Math.round(rng / ps) * ps);
            
            picked = Math.round(data.length - (rotation % 360)/ps);
            picked = picked >= data.length ? (picked % data.length) : picked;


            if(oldpick.indexOf(picked) !== -1){
                d3.select(this).call(spin);
                return;
            } else {
                oldpick.push(picked);
            }

            rotation += 90 - Math.round(ps/2);

            vis.transition()
                .duration(3000)
                .attrTween("transform", rotTween)
                .each("end", function(){

                    //mark question as seen
                    d3.select(".slice:nth-child(" + (picked + 1) + ") path")
                        .attr("fill", "#111");

                    //populate question
                    d3.select("#question h1")
                        .text(data[picked].question);

                    oldrotation = rotation;
                
                    container.on("click", spin);
                });
        }

        //make arrow
        svg.append("g")
            .attr("transform", "translate(" + (w + padding.left + padding.right) + "," + ((h/2)+padding.top) + ")")
            .append("path")
            .attr("d", "M-" + (r*.15) + ",0L0," + (r*.05) + "L0,-" + (r*.05) + "Z")
            .style({"fill":"black"});

        //draw spin circle
        container.append("circle")
            .attr("cx", 0)
            .attr("cy", 0)
            .attr("r", 60)
            .style({"fill":"white","cursor":"pointer"});

        //spin text
        container.append("text")
            .attr("x", 0)
            .attr("y", 15)
            .attr("text-anchor", "middle")
            .text("SPIN")
            .style({"font-weight":"bold", "font-size":"30px"});
        
        
        function rotTween(to) {
          var i = d3.interpolate(oldrotation % 360, rotation);
          return function(t) {
            return "rotate(" + i(t) + ")";
          };
        }
        
        
        function getRandomNumbers(){
            var array = new Uint16Array(1000);
            var scale = d3.scale.linear().range([360, 1440]).domain([0, 100000]);

            if(window.hasOwnProperty("crypto") && typeof window.crypto.getRandomValues === "function"){
                window.crypto.getRandomValues(array);
                console.log("works");
            } else {
                //no support for crypto, get crappy random numbers
                for(var i=0; i < 1000; i++){
                    array[i] = Math.floor(Math.random() * 100000) + 1;
                }
            }

            return array;
        }
text{
        font-family:Helvetica, Arial, sans-serif;
        font-size:11px;
        pointer-events:none;
    }
    #chart{
        position:absolute;
        width:500px;
        height:500px;
        top:0;
        left:0;
    }
    #question{
        position: absolute;
        width:400px;
        height:500px;
        top:0;
        left:520px;
    }
    #question h1{
        font-size: 50px;
        font-weight: bold;
        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
        position: absolute;
        padding: 0;
        margin: 0;
        top:50%;
        -webkit-transform:translate(0,-50%);
                transform:translate(0,-50%);
    }
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<div id="chart"></div>
<div id="question"><h1></h1></div>
  

This will Help You Orginal Source : https://gist.github.com/jrue/a2aaf36b3c096925ccbf

Guest
  • 1