首页 > 解决方案 > 如何在 Swift 中触摸和拖动来选择视图中的区域?

问题描述

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
}

标签: iosswiftuiimageviewtouchesbegan

解决方案


如果您想在选择完图像的一部分后做某事,例如,实现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”并想要对其进行快照,您可能会选择不同的快照算法,例如https://stackoverflow.com/a/54191120/1271826中所述。


推荐阅读