Swift: сделать пользовательский просмотр прогресса после нажатия пользователя?UIBezier - PullRequest
0 голосов
/ 06 июня 2018

Хорошо. У меня здесь реализован пользовательский 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 (_ touch: Установить, с событием: UIEvent?) {Если let touch = touches.first {let currentPoint = touch.location (in: self) // сделать что-то с вашей текущей точкой

    //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 голосов
/ 06 июня 2018

Держитесь крепче, это требует математики!

Предполагается, что центр вашего круга (0, 0).Допустим, у вас есть точка (x, y), лежащая на окружности, вы можете использовать функцию atan2 или arctan2 , чтобы получить угол (в радианах; радиан - это то, что вы хотите, так как 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) - это точки, по которым пользователь коснулся окружности.

Пример и пояснение (Правка)

Ладно тьфу , после некоторых исследований я понял это. посмотрите на этот код , обратите внимание на функцию 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 Angle до touch Angle, как упоминалось ранее ( использовать этот метод для рисования штрихов напрямуюс UIBezierPath в draw(_ ... методе ), но я использую эти значения для вычисления доли perc (между 0 и 1) и использую это как stroke end дуги прогресса.

Это приводит к интересному новому обучению.Значение atan(y/x) зависит от домена ( За этим стоит математика, если вам интересно ).Так что , если вы касаетесь 4-го квадранта, вы добавляете 360˚ или 2π, а если вы находитесь во 2-м или 3-м квадранте, вы добавляете 180˚ или π .После этого добавления ваш touch angle должен отражать идеальное значение.

...