如何在Swift中触摸和拖动视图中选择区域?

时间:2019-01-18 16:24:56

标签: ios swift uiimageview touchesbegan

我需要在我的应用程序中实现一项功能,该功能允许用户只需触摸和拖动即可在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
}

1 个答案:

答案 0 :(得分:2)

如果要在选择完图像的一部分后想做些事情,例如,实施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。除非您有其他打算,否则似乎没有必要。

  • 我不确定您用于图像视图的内容模式。例如,如果您正在使用“纵横比拟合”并想要对其进行快照,则可以选择其他快照算法,例如https://stackoverflow.com/a/54191120/1271826中概述的算法。