Swift:自定义进度视图跟随用户点击? UIBezier

时间:2018-06-06 00:15:11

标签: swift swift3 uiview path uiprogressview

好的我在这里实现了一个自定义的UIProgressView形状:

class PlaybackLineView: UIView {
    var percentage: Float = 0.0 {
        didSet {
            if percentage > 1 {
                //print("happned!")
                percentage = 1.0
            }

            DispatchQueue.main.async {
                self.setNeedsDisplay()
            }
        }
    }

    override func draw(_ rect: CGRect) {
        let playbackRed = UIColor(red: 181/255.0, green: 23/255.0, blue: 65/255.0, alpha: 1.0)
        playbackRed.setFill()
        playbackRed.setStroke()

        let newWidth = rect.size.width * CGFloat(percentage)
        let fillRect = CGRect(x: 0, y: 0, width: newWidth, height: rect.size.height)
        let fillRectPath = UIBezierPath(rect: fillRect)
        fillRectPath.fill()
    }

@IBInspectable public var drawEndPoint: Bool = true
    @IBInspectable public var drawWhenZero: Bool = false
    @IBInspectable public var strokeWidth: CGFloat = 3.0

    let smallCircleRadius: CGFloat = 6.0
    var fullCirclePath: UIBezierPath!


    override func draw(_ rect: CGRect) {
        if !drawWhenZero && (percentage == 0 || percentage == 1) { return }

        UIColor.clear.setFill()

        let arcCenter = CGPoint(x: rect.midX, y: rect.midY)
        let radius = (rect.width - smallCircleRadius * 2) / 2.0

        let circleGray = UIColor(red: 218/255.0, green: 218/255.0, blue: 218/255.0, alpha: 1.0)
        circleGray.setStroke()
        fullCirclePath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: 0.0, endAngle:CGFloat.pi * 2, clockwise: true)
        fullCirclePath.lineWidth = strokeWidth
        fullCirclePath.fill()
        fullCirclePath.stroke()

        let circleRed = UIColor(red: 181/255.0, green: 23/255.0, blue: 65/255.0, alpha: 1.0)
        circleRed.setStroke()
        let arcPath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: 0.0, endAngle:CGFloat.pi * 2 * CGFloat(percentage), clockwise: true)
        arcPath.fill()
        arcPath.stroke()
        arcPath.lineWidth = strokeWidth

        if !drawEndPoint { return }

        let smallCirclePath = UIBezierPath(arcCenter: arcPath.currentPoint, radius: smallCircleRadius, startAngle: 0.0, endAngle:CGFloat.pi * 2, clockwise: true)
        circleRed.setFill()
        smallCirclePath.fill()
        smallCirclePath.stroke()

        self.transform = CGAffineTransform(rotationAngle: (-90).degreesToRadians)
    }

根据percentage,这会沿着较大圆圈的路径移动小圆圈。问题是我需要允许用户通过点击/擦洗UIBezierPath来控制这个百分比 -

到目前为止,我已经得到了这个,以确定水龙头是否在更大的圈内:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            let currentPoint = touch.location(in: self)
            // do something with your currentPoint

            if(fullCirclePath.contains(currentPoint))
            {
                print("CONTAINS!: ", currentPoint)
                //percentage = 0.5
            }
        }
    }

如何从这个CGPoint获得沿着路径的水龙头的较大圆圈的百分比(以百分比表示)?

我尝试了什么:

覆盖func touchesBegan(_ touches:Set,with event:UIEvent?){     如果让touch = touches.first {         let currentPoint = touch.location(in:self)         //用你的currentPoint做点什么

    //print(fullCirclePath.cgPath.cen)
    if(fullCirclePath.contains(currentPoint))
    {

        touchAngle = atan2((currentPoint.x-arcCenter.x), (currentPoint.y-arcCenter.y))
        percentage = Float(touchAngle * 100)/Float(M_PI * 2)
        print("CONTAINS!: ", currentPoint, "Angle: ", touchAngle.radiansToDegrees, " Percent: ", percentage)

        if(AudioPlayerManager.shared.isPlaying())
        {
            AudioPlayerManager.shared.seek(toProgress: percentage)
        }
    }
}

}

1 个答案:

答案 0 :(得分:1)

紧紧抓住这需要一些数学!

假设你的圆圈的中心是(0,0)。假设您有一个点(x,y)位于圆周上,您可以使用atan2 or arctan2 function来获得角度(以弧度为单位; 弧度是您想要的UIBezierPath使用弧度)该点位于圆的中心。因此,使用此角度,您可以知道用户触摸的圆周上的位置(称为touch angle)。您可以将其用作endAngle上的UIBezierPath

注意:如果您的圆圈中心不是(0,0)但是(x1,y1),则通过减去触点(x,y)的差异进行补偿,即您的新点数是(x - x1,y - y1)

Angles in the default coordinate system

如果你想要一个百分比,该点的圆周覆盖范围是0拉德。您可以使用公式percentage = (touch angle*(in rads)* * 100)/2π)

  

更多关于atan2 / arctan2

您可以使用公式touch angle = tan^(-1)(y/x)又名touch angle = arctan2(y,x)计算您的触控角度。其中(x,y)是用户在圆周上触及的点。

  

示例和说明(编辑)

好的 phew ,经过一番研究,我发现了它。 取look at this code,注意touchesBegan(_ ...功能。

public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    for touch in touches {
        let point = touch.location(in: self)
        if backCircleBezierPath!.contains(point) {
            print("contains")
            let centeredPoint = CGPoint(x: (point.x - frame.width/2), y: (point.y - frame.height/2))
            print(centeredPoint)
            var rads = atan(centeredPoint.y/centeredPoint.x)
            if centeredPoint.x < 0 {
                rads += CGFloat.pi
            } else if centeredPoint.x > 0 && centeredPoint.y < 0 {
                rads += CGFloat.pi * 2
            }
            let perc = (rads - backCircleStartAngle) / abs(backCircleStartAngle - backCircleEndAngle)
            frontCircleShapeLayer?.strokeEnd = perc
        }
    }
}

我使用CAShapeLayer代替了draw(_ rect...;我建议你这样做。你的方式也可以正常工作,但最后我会提到一些变化。

我使用了两个弧: 1.占位符弧 - backCircleShapeLayer 2.进度指示器弧 - frontCircleShapeLayer

当用户点击占位符弧时,我们可以计算角度(以弧度为单位)并从前面提到的start Angletouch Angle绘制进度指示器弧(使用此方法直接使用UIBezierPath方法中的draw(_ ...绘制笔画,但我正在做的是使用这些值计算分数perc(介于0和1之间)并使用那是进度弧的stroke end

这带来了一些有趣的新学习。 atan(y/x)的值是特定于域的(Some math behind this, if you're interested)。所以如果您的触摸位于第四象限,则添加360˚或2π,如果它位于第二或第三象限,则添加180˚或π。添加完成后,touch angle应该反映出完美的价值。