Swift - 使用蓝色拖线在两个标签或按钮之间创建连接,如 Xcode

Swift - Creating a connection between two labels or button using blue drag line like in Xcode

我已经成功地像 Xcode 那样创建了蓝线,但是当线的终点被拖动并释放到其他标签或按钮上方以便建立连接时,您如何识别?

代码

class ViewController: UIViewController {

@IBOutlet var label: UILabel!

@IBOutlet var label2: UILabel!


private lazy var lineShape: CAShapeLayer = {
    var color = hexStringToUIColor(hex: "#5DBCD2")
    let lineShape = CAShapeLayer()
    lineShape.strokeColor = UIColor.blue.cgColor
    lineShape.fillColor = color.cgColor
    lineShape.lineWidth = 2.0

    return lineShape
}()
private var panGestureStartPoint: CGPoint = .zero
private lazy var panRecognizer: UIPanGestureRecognizer = {
    return UIPanGestureRecognizer(target: self, action: #selector(panGestureCalled(_:)))
}()

override func viewDidLoad() {
    super.viewDidLoad()

    self.label.addGestureRecognizer(panRecognizer)
}

@objc func panGestureCalled(_: UIPanGestureRecognizer) {
    let currentPanPoint = panRecognizer.location(in: self.view)
    switch panRecognizer.state {
    case .began:
        panGestureStartPoint = currentPanPoint
        self.view.layer.addSublayer(lineShape)

    case .changed:
        let linePath = UIBezierPath()
        linePath.move(to: panGestureStartPoint)
        linePath.addLine(to: currentPanPoint)

        lineShape.path = linePath.cgPath
        lineShape.path = CGPath.barbell(from: panGestureStartPoint, to: currentPanPoint, barThickness: 2.0, bellRadius: 6.0)

    case .ended:
        lineShape.path = nil
        lineShape.removeFromSuperlayer()
    default: break
    }
}

func hexStringToUIColor (hex:String) -> UIColor {
    var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()

    if (cString.hasPrefix("#")) {
        cString.remove(at: cString.startIndex)
    }

    if ((cString.count) != 6) {
        return UIColor.gray
    }

    var rgbValue:UInt32 = 0
    Scanner(string: cString).scanHexInt32(&rgbValue)

    return UIColor(
        red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
        green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
        blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
        alpha: CGFloat(1.0)
    )
}
}

extension CGPath {
    class func barbell(from start: CGPoint, to end: CGPoint, barThickness proposedBarThickness: CGFloat, bellRadius proposedBellRadius: CGFloat) -> CGPath {
        let barThickness = max(0, proposedBarThickness)
        let bellRadius = max(barThickness / 2, proposedBellRadius)

        let vector = CGPoint(x: end.x - start.x, y: end.y - start.y)
        let length = hypot(vector.x, vector.y)

        if length == 0 {
            return CGPath(ellipseIn: CGRect(origin: start, size: .zero).insetBy(dx: -bellRadius, dy: -bellRadius), transform: nil)
        }

        var yOffset = barThickness / 2
        var xOffset = sqrt(bellRadius * bellRadius - yOffset * yOffset)
        let halfLength = length / 2
        if xOffset > halfLength {
            xOffset = halfLength
            yOffset = sqrt(bellRadius * bellRadius - xOffset * xOffset)
        }

        let jointRadians = asin(yOffset / bellRadius)
        let path = CGMutablePath()
        path.addArc(center: .zero, radius: bellRadius, startAngle: jointRadians, endAngle: -jointRadians, clockwise: false)
        path.addArc(center: CGPoint(x: length, y: 0), radius: bellRadius, startAngle: .pi + jointRadians, endAngle: .pi - jointRadians, clockwise: false)
        path.closeSubpath()

        let unitVector = CGPoint(x: vector.x / length, y: vector.y / length)
        var transform = CGAffineTransform(a: unitVector.x, b: unitVector.y, c: -unitVector.y, d: unitVector.x, tx: start.x, ty: start.y)
        return path.copy(using: &transform)!
    }
}

上面的代码复制了 gif 动画。如果有帮助,我使用下面的问题来创建它。

我认为答案在上面的问题中,但我无法理解它,因为它适用于 MacOS 而不是 iOS。

非常感谢任何帮助

更新

根据给出的非常好的答案,我在 AddedPanGesture() 中添加了以下代码。这不是代码的完成版本。

 case .changed:
        let linePath = UIBezierPath()
        linePath.move(to: panGestureStartPoint)
        linePath.addLine(to: currentPanPoint)

        lineShape.path = linePath.cgPath
        lineShape.path = CGPath.barbell(from: panGestureStartPoint, to: currentPanPoint, barThickness: 2.0, bellRadius: 6.0)

        let labels = [label2, label3]
        for labelPoint in labels {
            let point = panRecognizer.location(in: labelPoint)

            if labelPoint!.layer.contains(point){
                labelPoint!.layer.borderWidth =  10
                labelPoint!.layer.borderColor =  UIColor.green.cgColor
            } else {
                labelPoint!.layer.borderWidth =  0
                labelPoint!.layer.borderColor =  UIColor.clear.cgColor

            }
        }

    case .ended:
        let labels = [label2, label3]
        for labelPoint in labels {
            let point = panRecognizer.location(in: labelPoint)

            if labelPoint!.layer.contains(point){
                labelPoint!.layer.borderWidth =  2
                labelPoint!.layer.borderColor =  UIColor.green.cgColor
            } else {
                lineShape.path = nil
                lineShape.removeFromSuperlayer()
                labelPoint!.layer.borderWidth =  0
                labelPoint!.layer.borderColor =  UIColor.clear.cgColor

            }
        }

如果有更好的方法我会喜欢的输入。 谢谢!

CGRect 实现了一个 contains(_:) 方法,可以让您判断一个矩形是否包含一个点。

您需要添加逻辑来确保您的起始拖动点在一个按钮内,并且您的终点在另一个按钮内,使用这些按钮的框架作为您询问的矩形。如果您发现用户已从一个按钮拖动到下一个按钮,那么您的 .ended 处理程序(如 M Abubaker Majeed 在 his/her 评论中提到的那样)需要检测到这一点并添加永久 link不知何故。你如何做到这一点取决于你。您可以使用单独的形状层来保存您想要保留的线条,以及连接线的 starting/ending 点数组,或各种其他方法。

为了您的方便,我只是更新了代码。

 @objc func panGestureCalled(_: UIPanGestureRecognizer) {
    let currentPanPoint = panRecognizer.location(in: self.view)
    switch panRecognizer.state {
    case .began:
        panGestureStartPoint = currentPanPoint
        self.view.layer.addSublayer(lineShape)

    case .changed:
        let linePath = UIBezierPath()
        linePath.move(to: panGestureStartPoint)
        linePath.addLine(to: currentPanPoint)

        lineShape.path = linePath.cgPath
        lineShape.path = CGPath.barbell(from: panGestureStartPoint, to: currentPanPoint, barThickness: 2.0, bellRadius: 6.0)


        label2.layer.borderWidth =  hasDestine(panRecognizer) ? 10 :   0
        label2.layer.borderColor =  hasDestine(panRecognizer) ? UIColor.green.cgColor :   UIColor.clear.cgColor

    case .ended:
        if  (!hasDestine(panRecognizer)){
        lineShape.path = nil
        lineShape.removeFromSuperlayer()}
        label2.layer.borderWidth =   0
    default: break
    }
}

func hasDestine(_ panRecognizer: UIPanGestureRecognizer)-> Bool {
    let point = panRecognizer.location(in: label2)
    return   label2.layer.contains(point)
}