Swift 3:如何在ScrollView内的图片上绘制线条?
It’s super common to run into coordinate mismatch issues when drawing on a zoomable/scrolling image—let’s walk through the key fixes to get your rectangles showing up properly.
1. Convert Touch Coordinates to the Image’s Actual Space
The first problem is almost always that your touch points are in the scroll view’s coordinate system, not the image’s scaled bounds. You need to adjust for the scroll view’s zoom scale and content offset, plus the image view’s content mode (like aspect fit).
Here’s a helper method to convert a touch location to the image’s native coordinates:
func convertTouchPoint(_ point: CGPoint) -> CGPoint { let scrollView = self.scrollView! let imageView = self.photoImageView! // Calculate the scaled bounds of the image inside the image view let imageSize = imageView.image?.size ?? .zero guard imageSize.width > 0 && imageSize.height > 0 else { return point } let scaleWidth = imageView.bounds.width / imageSize.width let scaleHeight = imageView.bounds.height / imageSize.height let imageScale = min(scaleWidth, scaleHeight) let scaledImageWidth = imageSize.width * imageScale let scaledImageHeight = imageSize.height * imageScale // Calculate the offset of the image within the image view (for aspect fit) let xOffset = (imageView.bounds.width - scaledImageWidth) / 2 let yOffset = (imageView.bounds.height - scaledImageHeight) / 2 // Adjust the touch point for scroll view zoom and offset let adjustedPoint = CGPoint( x: (point.x - scrollView.contentOffset.x - xOffset) / (scrollView.zoomScale * imageScale), y: (point.y - scrollView.contentOffset.y - yOffset) / (scrollView.zoomScale * imageScale) ) return adjustedPoint }
2. Use a CAShapeLayer for Dynamic Drawing
Instead of drawing directly into the UIImage (which is static), add a CAShapeLayer on top of your image view. This layer will update in real-time as the user draws and stay synced with the scroll view’s zoom/scroll.
Add these properties to your PhotoZoomController:
private var shapeLayer: CAShapeLayer! private var currentPath: UIBezierPath? private var startPoint: CGPoint? private var drawnRectCount = 0 // Track to limit to 2 rectangles
Then set up the layer in viewDidLoad:
override func viewDidLoad() { super.viewDidLoad() // Configure scroll view (make sure you set delegate and zoom limits) scrollView.delegate = self scrollView.minimumZoomScale = 1.0 scrollView.maximumZoomScale = 5.0 // Create shape layer for drawing shapeLayer = CAShapeLayer() shapeLayer.strokeColor = UIColor.red.cgColor shapeLayer.lineWidth = 2.0 shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineCap = .round photoImageView.layer.addSublayer(shapeLayer) }
3. Implement Touch Handling Methods
Update your touch methods to use the coordinate conversion, limit to 2 rectangles, and update the shape layer’s path:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard drawnRectCount < 2, let touch = touches.first else { return } let touchPoint = touch.location(in: scrollView) startPoint = convertTouchPoint(touchPoint) currentPath = UIBezierPath() } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard drawnRectCount < 2, let touch = touches.first, let startPoint = startPoint, var currentPath = currentPath else { return } let touchPoint = touch.location(in: scrollView) let endPoint = convertTouchPoint(touchPoint) // Reset path and draw rectangle from start to end point currentPath.removeAllPoints() currentPath.move(to: startPoint) currentPath.addLine(to: CGPoint(x: endPoint.x, y: startPoint.y)) currentPath.addLine(to: endPoint) currentPath.addLine(to: CGPoint(x: startPoint.x, y: endPoint.y)) currentPath.close() shapeLayer.path = currentPath.cgPath } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { guard drawnRectCount < 2 else { return } // Optional: Save the path if you need to keep both rectangles visible // For example, store completed paths in an array and redraw all drawnRectCount += 1 currentPath = nil startPoint = nil }
4. Handle Scroll View Zoom
Make sure your scroll view delegate implements viewForZooming(in:) to keep the drawing layer synced with the image:
extension PhotoZoomController: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { return photoImageView } // Optional: If you need to adjust the layer after zooming (rare if using sublayer) func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { // No action needed here if shapeLayer is a sublayer of photoImageView // It will scale automatically with the image view } }
Key Notes
- Persist Multiple Rectangles: If you want both rectangles to stay visible, store each completed
UIBezierPathin an array. Then create a combined path from all stored paths and set it to the shape layer’s path. - Adjust Styling: Tweak the shape layer’s
strokeColor,lineWidth, orfillColorto match your app’s design.
Let me know if you run into any issues with specific parts of your existing code—this should cover the main reasons your lines aren’t showing up!
内容的提问来源于stack exchange,提问作者Richard Sun

