如何 select 只需在 Swift 中触摸并拖动视图中的一个区域?

How to select an area in a view just touching and dragging in Swift?

我需要在我的应用程序中实现一项功能,允许用户 select imageView 中的一个区域,只需触摸和拖动即可。

我试过 touchesBegan,但由于我是 Swift 的新手,我遇到了一些困难。

我该怎么做?

我到了这里,但是现在呢?

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    touch = touches.first
    lastPoint = touch.location(in: imageView)

    for touch in touches {
        print(touch.location(in: imageView))
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    touch = touches.first
    currentPoint = touch.location(in: imageView)

    self.imageView.setNeedsDisplay()

    lastPoint = currentPoint
}

如果您在选择完图像部分后想做一些事情,例如,实施 touchesEnded

例如,假设您希望在拖动时显示预期区域的矩形,并且希望在完成拖动后制作所选部分的图像快照。然后你可以做类似的事情:

@IBOutlet weak var imageView: UIImageView!

var startPoint: CGPoint?

let rectShapeLayer: CAShapeLayer = {
    let shapeLayer = CAShapeLayer()
    shapeLayer.strokeColor = UIColor.black.cgColor
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.lineWidth = 3
    return shapeLayer
}()

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    startPoint = nil

    guard let touch = touches.first else { return }

    startPoint = touch.location(in: imageView)

    // you might want to initialize whatever you need to begin showing selected rectangle below, e.g.

    rectShapeLayer.path = nil

    imageView.layer.addSublayer(rectShapeLayer)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first, let startPoint = startPoint else { return }

    let currentPoint: CGPoint

    if let predicted = event?.predictedTouches(for: touch), let lastPoint = predicted.last {
        currentPoint = lastPoint.location(in: imageView)
    } else {
        currentPoint = touch.location(in: imageView)
    }

    let frame = rect(from: startPoint, to: currentPoint)

    // you might do something with `frame`, e.g. show bounding box

    rectShapeLayer.path = UIBezierPath(rect: frame).cgPath
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first, let startPoint = startPoint else { return }

    let currentPoint = touch.location(in: imageView)
    let frame = rect(from: startPoint, to: currentPoint)

    // you might do something with `frame`, e.g. remove bounding box but take snapshot of selected `CGRect`

    rectShapeLayer.removeFromSuperlayer()
    let image = imageView.snapshot(rect: frame, afterScreenUpdates: true)

    // do something with this `image`
}

private func rect(from: CGPoint, to: CGPoint) -> CGRect {
    return CGRect(x: min(from.x, to.x),
           y: min(from.y, to.y),
           width: abs(to.x - from.x),
           height: abs(to.y - from.y))
}

你在哪里有这个 UIView 用于创建快照图像的扩展:

extension UIView {

    /// Create image snapshot of view.
    ///
    /// - Parameters:
    ///   - rect: The coordinates (in the view's own coordinate space) to be captured. If omitted, the entire `bounds` will be captured.
    ///   - afterScreenUpdates: A Boolean value that indicates whether the snapshot should be rendered after recent changes have been incorporated. Specify the value false if you want to render a snapshot in the view hierarchy’s current state, which might not include recent changes.
    /// - Returns: The `UIImage` snapshot.

    func snapshot(rect: CGRect? = nil, afterScreenUpdates: Bool = true) -> UIImage {
        return UIGraphicsImageRenderer(bounds: rect ?? bounds).image { _ in
            drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
        }
    }
}

现在,如果你想做一些事情而不是捕捉图像的快照,那么,好吧,做任何你想做的事。但这说明了基本思想。


在我上面的例子中有几件小事:

  • 请注意,我将我的 ivars 限制在我绝对需要的东西上。例如。当前的 touch 应该是局部变量,而不是 ivar。我们应该始终将我们的变量限制在尽可能窄的范围内,以避免意外后果等。

  • 我对您的 touchesMoved 进行了细微的改进以使用预测触摸。这不是必需的,但可以帮助最大程度地减少拖动手指时感觉到的任何延迟。

  • 我完全不知道你为什么打电话给 setNeedsDisplay。除非您打算在那里做其他事情,否则这似乎是不必要的。

  • 我不确定您的图像视图使用的是什么内容模式。例如,如果您正在使用“Aspect scale fit”并想要对其进行快照,您可能会选择不同的快照算法,例如 .

  • 中概述的算法