在另一个圆形 UIView 中移动一个圆形 UIView

Move a circular UIView inside another circular UIView

我正在尝试做一个操纵杆swift,我快完成了。

但是我有一个问题,当我移动摇杆时,摇杆的运动是平滑的"in the middle",但是当摇杆接触到"its container"的边缘时,它变得迟钝。

但我知道为什么,因为我只允许摇杆在不碰到边缘的情况下移动,我不知道如何纠正这个问题(在else中放什么代码)。

这是我的代码和一个 GIF,以便您看得更清楚。

import UIKit
import SnapKit

class ViewController: UIViewController {

    let joystickSize = 150
    let substractSize = 200
    let joystickOffset = 10

    let joystickSubstractView = UIView()
    let joystickView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        joystickSubstractView.backgroundColor = .gray
        joystickSubstractView.layer.cornerRadius = CGFloat(substractSize / 2)
        self.view.addSubview(joystickSubstractView)

        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragJoystick))
        joystickView.isUserInteractionEnabled = true
        joystickView.addGestureRecognizer(panGesture)
        joystickView.backgroundColor = .white
        joystickView.layer.cornerRadius = CGFloat(joystickSize / 2)
        joystickSubstractView.addSubview(joystickView)

        joystickSubstractView.snp.makeConstraints {
            [=10=].width.height.equalTo(substractSize)
            [=10=].centerX.equalToSuperview()
            [=10=].bottom.equalToSuperview().inset(150)
        }

        joystickView.snp.makeConstraints {
            [=10=].width.height.equalTo(joystickSize)
            [=10=].center.equalToSuperview()
        }
    }

    @objc func dragJoystick(_ sender: UIPanGestureRecognizer) {
        self.view.bringSubviewToFront(joystickView)
        let translation = sender.translation(in: self.view)

        let joystickCenter = joystickView.convert(joystickView.center, to: self.view)
        let futureJoystickCenter =  CGPoint(x: joystickCenter.x - joystickView.frame.minX + translation.x,
                                            y: joystickCenter.y - joystickView.frame.minY + translation.y)
        let distanceBetweenCenters = hypot(futureJoystickCenter.x - joystickSubstractView.center.x,
                                           futureJoystickCenter.y - joystickSubstractView.center.y)

        if CGFloat(substractSize / 2 + joystickOffset) >= (distanceBetweenCenters + CGFloat(joystickSize / 2)) {
            joystickView.center = CGPoint(x: joystickView.center.x + translation.x,
                                          y: joystickView.center.y + translation.y)
        } else {
            // I don't know what to put here to make the joystick "smoother"
        }

        sender.setTranslation(CGPoint.zero, in: self.view)
    }
}

感谢您的帮助

这是一种方法...

  • 计算外圆圆心到内圆圆心的最大可用距离,作为半径
  • 跟踪触摸/平移手势相对于外圆中心的位置
  • 如果新的内圆圆心(触摸点)到外圆圆心的距离大于最大半径,将内圆圆心移动到touch-to-center的交点直线和半径圆的边缘

这是它的外观,"joystick" 视图的中心用绿点标识,半径圆显示为红色轮廓:

您可以尝试使用此代码:

class JoyStickViewController: UIViewController {

    let joystickSize: CGFloat = 150
    let substractSize: CGFloat = 200

    var innerRadius: CGFloat = 0.0

    let joystickSubstractView = UIView()
    let joystickView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        joystickSubstractView.backgroundColor = .gray
        joystickSubstractView.layer.cornerRadius = CGFloat(substractSize / 2)
        self.view.addSubview(joystickSubstractView)

        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragJoystick(_:)))
        joystickView.isUserInteractionEnabled = true
        joystickView.addGestureRecognizer(panGesture)
        joystickView.backgroundColor = .yellow
        joystickView.layer.cornerRadius = CGFloat(joystickSize / 2)
        joystickSubstractView.addSubview(joystickView)

        joystickSubstractView.snp.makeConstraints {
            [=10=].width.height.equalTo(substractSize)
            [=10=].centerX.equalToSuperview()
            [=10=].bottom.equalToSuperview().inset(150)
        }

        joystickView.snp.makeConstraints {
            [=10=].width.height.equalTo(joystickSize)
            [=10=].center.equalToSuperview()
        }

        // if you want the "joystick" circle to overlap the "outer circle" a bit, adjust this value
        innerRadius = (substractSize - joystickSize) * 0.5


        // start debugging / clarification...
        // add a center "dot" to the joystick view
        // add a red circle showing the inner radius - where we want to restrict the center of the joystick view
        let jsCenterView = UIView()
        jsCenterView.backgroundColor = .green
        jsCenterView.layer.cornerRadius = 2.0
        joystickView.addSubview(jsCenterView)
        jsCenterView.snp.makeConstraints {
            [=10=].width.height.equalTo(4.0)
            [=10=].center.equalToSuperview()
        }

        let v = UIView()
        v.backgroundColor = .clear
        v.layer.borderColor = UIColor.red.cgColor
        v.layer.borderWidth = 2
        v.layer.cornerRadius = innerRadius
        v.isUserInteractionEnabled = false
        joystickSubstractView.addSubview(v)
        v.snp.makeConstraints {
            [=10=].width.height.equalTo(innerRadius * 2.0)
            [=10=].center.equalToSuperview()
        }

        // end debugging / clarification

    }

    func lineLength(from pt1: CGPoint, to pt2: CGPoint) -> CGFloat {
        return hypot(pt2.x - pt1.x, pt2.y - pt1.y)
    }

    func pointOnLine(from startPt: CGPoint, to endPt: CGPoint, distance: CGFloat) -> CGPoint {
        let totalDistance = lineLength(from: startPt, to: endPt)
        let totalDelta = CGPoint(x: endPt.x - startPt.x, y: endPt.y - startPt.y)
        let pct = distance / totalDistance;
        let delta = CGPoint(x: totalDelta.x * pct, y: totalDelta.y * pct)
        return CGPoint(x: startPt.x + delta.x, y: startPt.y + delta.y)
    }

    @objc func dragJoystick(_ sender: UIPanGestureRecognizer) {

        let touchLocation = sender.location(in: joystickSubstractView)

        let outerCircleViewCenter = CGPoint(x: joystickSubstractView.bounds.width * 0.5, y: joystickSubstractView.bounds.height * 0.5)

        var newCenter = touchLocation

        let distance = lineLength(from: touchLocation, to: outerCircleViewCenter)

        // if the touch would put the "joystick circle" outside the "outer circle"
        // find the point on the line from center to touch, at innerRadius distance
        if distance > innerRadius {
            newCenter = pointOnLine(from: outerCircleViewCenter, to: touchLocation, distance: innerRadius)
        }

        joystickView.center = newCenter

    }

}

注意:您可以删除(或注释掉)viewDidLoad()// start debugging// end debugging注释之间的代码行删除绿色中心点和红色圆圈。