首页 > 解决方案 > Swift - 使用 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 动画。如果有帮助,我使用下面的问题来创建它。

画一条可以像 Swift 中的 Xcode 助手编辑器一样伸展的线

如何重现这条 Xcode 蓝色拖线

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

任何帮助深表感谢

更新

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

 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

            }
        }

在此处输入图像描述

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

标签: iosswiftuiviewuigesturerecognizercashapelayer

解决方案


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

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


推荐阅读