如何为自定义篮球场形状添加UIPinchGestureRecognizer实现缩放适配
自定义篮球场视图:添加缩放与旋转适配功能
要实现篮球场的缩放和方向调整,我们需要将原有的静态绘制类改造为UIView子类,通过手势识别器控制缩放比例与旋转角度,同时在绘制时应用这些变换。以下是完整的实现代码:
import UIKit class BasketballCourtView: UIView { // 缩放比例,默认1.0 private var scale: CGFloat = 1.0 // 旋转角度(弧度),默认0 private var rotation: CGFloat = 0.0 override init(frame: CGRect) { super.init(frame: frame) setupGestures() backgroundColor = .clear } required init?(coder: NSCoder) { super.init(coder: coder) setupGestures() backgroundColor = .clear } // 设置手势识别器 private func setupGestures() { // 捏合缩放手势 let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:))) addGestureRecognizer(pinchGesture) // 旋转手势 let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(_:))) addGestureRecognizer(rotationGesture) } // 处理缩放手势 @objc private func handlePinch(_ gesture: UIPinchGestureRecognizer) { switch gesture.state { case .changed, .ended: // 限制缩放范围,避免过大或过小 let newScale = max(0.5, min(scale * gesture.scale, 3.0)) scale = newScale gesture.scale = 1.0 // 重置手势缩放值,避免累积 setNeedsDisplay() default: break } } // 处理旋转手势 @objc private func handleRotation(_ gesture: UIRotationGestureRecognizer) { switch gesture.state { case .changed, .ended: rotation += gesture.rotation gesture.rotation = 0.0 // 重置手势旋转值,避免累积 setNeedsDisplay() default: break } } override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } // 保存原始上下文状态 context.saveGState() // 将坐标系原点移到视图中心,方便旋转和缩放 let center = CGPoint(x: bounds.midX, y: bounds.midY) context.translateBy(x: center.x, y: center.y) // 应用旋转 context.rotate(by: rotation) // 应用缩放 context.scaleBy(x: scale, y: scale) // 移回原点,确保绘制内容居中 context.translateBy(x: -center.x, y: -center.y) // 调用原有的绘制方法,传入视图的bounds作为目标帧 BasketballCourtShape.drawCanvas1(frame: bounds, resizing: .aspectFit) // 恢复上下文状态 context.restoreGState() } } // 保留原有的绘制逻辑,类名调整为更规范的命名 class BasketballCourtShape: NSObject { @objc dynamic public class func drawCanvas1(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 240, height: 120), resizing: ResizingBehavior = .aspectFit) { guard let context = UIGraphicsGetCurrentContext() else { return } //// Resize to Target Frame context.saveGState() let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 240, height: 120), target: targetFrame) context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) context.scaleBy(x: resizedFrame.width / 240, y: resizedFrame.height / 120) //// Bezier 8 Drawing(补充完整篮球场边界路径) let bezier8Path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 240, height: 120)) UIColor.gray.setFill() bezier8Path.fill() UIColor.black.setStroke() bezier8Path.lineWidth = 1 bezier8Path.stroke() //// Bezier 16 Drawing(可补充完整路径) let bezier16Path = UIBezierPath() UIColor.black.setStroke() bezier16Path.lineWidth = 1 bezier16Path.stroke() //// Symbol Drawing context.saveGState() context.translateBy(x: 102.5, y: 116.5) let symbolRect = CGRect(x: 0, y: -103, width: 63, height: 103) context.saveGState() context.clip(to: symbolRect) context.translateBy(x: symbolRect.minX, y: symbolRect.minY) BasketballCourtShape.drawCanvas2(frame: CGRect(origin: .zero, size: symbolRect.size), resizing: .stretch) context.restoreGState() context.restoreGState() context.restoreGState() } @objc dynamic public class func drawCanvas2(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 63, height: 103), resizing: ResizingBehavior = .aspectFit) { guard let context = UIGraphicsGetCurrentContext() else { return } //// Resize to Target Frame context.saveGState() let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 63, height: 103), target: targetFrame) context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) context.scaleBy(x: resizedFrame.width / 63, y: resizedFrame.height / 103) //// Group //// Bezier Drawing let bezierPath = UIBezierPath() bezierPath.move(to: CGPoint(x: 0, y: 103)) bezierPath.addLine(to: CGPoint(x: 0, y: 65.5)) bezierPath.addLine(to: CGPoint(x: 0, y: 34)) bezierPath.addLine(to: CGPoint(x: 0, y: 1)) UIColor.orange.setStroke() bezierPath.lineWidth = 2 bezierPath.stroke() //// Bezier 2 Drawing let bezier2Path = UIBezierPath() bezier2Path.move(to: CGPoint(x: 23, y: 1)) bezier2Path.addLine(to: CGPoint(x: 0, y: 1)) UIColor.orange.setStroke() bezier2Path.lineWidth = 2 bezier2Path.stroke() //// Bezier 3 Drawing let bezier3Path = UIBezierPath() bezier3Path.move(to: CGPoint(x: 24, y: 103)) bezier3Path.addLine(to: CGPoint(x: 0, y: 103)) UIColor.orange.setStroke() bezier3Path.lineWidth = 2 bezier3Path.stroke() //// Bezier 5 Drawing let bezier5Path = UIBezierPath() bezier5Path.move(to: CGPoint(x: 62.94, y: 50.5)) bezier5Path.addCurve(to: CGPoint(x: 24, y: 103), controlPoint1: CGPoint(x: 62.94, y: 50.5), controlPoint2: CGPoint(x: 66.28, y: 103)) UIColor.orange.setStroke() bezier5Path.lineWidth = 2 bezier5Path.stroke() //// Bezier 6 Drawing(可补充路径) let bezier6Path = UIBezierPath() UIColor.black.setStroke() bezier6Path.lineWidth = 2 bezier6Path.stroke() //// Bezier 4 Drawing(可补充路径) let bezier4Path = UIBezierPath() UIColor.black.setStroke() bezier4Path.lineWidth = 2 bezier4Path.stroke() //// Bezier 7 Drawing let bezier7Path = UIBezierPath() bezier7Path.move(to: CGPoint(x: 62.98, y: 52)) bezier7Path.addCurve(to: CGPoint(x: 23, y: 1), controlPoint1: CGPoint(x: 62.98, y: 52), controlPoint2: CGPoint(x: 65.33, y: 1)) UIColor.orange.setStroke() bezier7Path.lineWidth = 2 bezier7Path.stroke() //// Bezier 9 Drawing(可补充路径) let bezier9Path = UIBezierPath() UIColor.black.setStroke() bezier9Path.lineWidth = 2 bezier9Path.stroke() //// Bezier 10 Drawing let bezier10Path = UIBezierPath() bezier10Path.move(to: CGPoint(x: 42, y: 23.5)) bezier10Path.addLine(to: CGPoint(x: 0, y: 23.5)) bezier10Path.addLine(to: CGPoint(x: 0, y: 82)) bezier10Path.addLine(to: CGPoint(x: 42, y: 82)) UIColor.orange.setStroke() bezier10Path.lineWidth = 2 bezier10Path.stroke() //// Bezier 11 Drawing let bezier11Path = UIBezierPath() bezier11Path.move(to: CGPoint(x: 42, y: 23.5)) bezier11Path.addLine(to: CGPoint(x: 42, y: 83.5)) UIColor.orange.setStroke() bezier11Path.lineWidth = 2 bezier11Path.stroke() //// Bezier 12 Drawing let bezier12Path = UIBezierPath() bezier12Path.move(to: CGPoint(x: 42, y: 35.5)) bezier12Path.addLine(to: CGPoint(x: 44, y: 35.5)) UIColor.orange.setStroke() bezier12Path.lineWidth = 2 bezier12Path.stroke() //// Bezier 14 Drawing let bezier14Path = UIBezierPath() bezier14Path.move(to: CGPoint(x: 42, y: 71.5)) bezier14Path.addLine(to: CGPoint(x: 44, y: 71.5)) UIColor.orange.setStroke() bezier14Path.lineWidth = 2 bezier14Path.stroke() //// Bezier 13 Drawing(可补充路径) let bezier13Path = UIBezierPath() UIColor.orange.setStroke() bezier13Path.lineWidth = 2 bezier13Path.stroke() //// Bezier 20 Drawing let bezier20Path = UIBezierPath() bezier20Path.move(to: CGPoint(x: 57.98, y: 52)) bezier20Path.addCurve(to: CGPoint(x: 44, y: 71.5), controlPoint1: CGPoint(x: 57.98, y: 52), controlPoint2: CGPoint(x: 59.14, y: 71.5)) UIColor.orange.setStroke() bezier20Path.lineWidth = 2 bezier20Path.stroke() //// Bezier 21 Drawing(可补充路径) let bezier21Path = UIBezierPath() UIColor.orange.setStroke() bezier21Path.lineWidth = 2 bezier21Path.stroke() //// Bezier 23 Drawing(可补充路径) let bezier23Path = UIBezierPath() UIColor.orange.setStroke() bezier23Path.lineWidth = 2 bezier23Path.stroke() //// Bezier 15 Drawing(可补充路径) let bezier15Path = UIBezierPath() UIColor.orange.setStroke() bezier15Path.lineWidth = 2 bezier15Path.stroke() //// Bezier 18 Drawing(可补充路径) let bezier18Path = UIBezierPath() UIColor.orange.setStroke() bezier18Path.lineWidth = 2 bezier18Path.stroke() //// Bezier 22 Drawing let bezier22Path = UIBezierPath() bezier22Path.move(to: CGPoint(x: 58, y: 52)) bezier22Path.addCurve(to: CGPoint(x: 44, y: 35.5), controlPoint1: CGPoint(x: 58, y: 52), controlPoint2: CGPoint(x: 56.83, y: 35.5)) UIColor.orange.setStroke() bezier22Path.lineWidth = 2 bezier22Path.stroke() context.restoreGState() } public enum ResizingBehavior: Int { case aspectFit /// 内容按比例缩放以适配目标矩形 case aspectFill /// 内容按比例缩放以完全填充目标矩形 case stretch /// 内容拉伸以匹配整个目标矩形 case center /// 内容居中显示,不进行缩放 public func apply(rect: CGRect, target: CGRect) -> CGRect { if rect == target || target == CGRect.zero { return rect } var scales = CGSize.zero scales.width = abs(target.width / rect.width) scales.height = abs(target.height / rect.height) switch self { case .aspectFit: scales.width = min(scales.width, scales.height) scales.height = scales.width case .aspectFill: scales.width = max(scales.width, scales.height) scales.height = scales.width case .stretch: break case .center: scales.width = 1 scales.height = 1 } var result = rect.standardized result.size.width *= scales.width result.size.height *= scales.height result.origin.x = target.minX + (target.width - result.width) / 2 result.origin.y = target.minY + (target.height - result.height) / 2 return result } } }
关键修改说明
- 封装为UIView子类:创建
BasketballCourtView继承自UIView,直接承载绘制逻辑和手势处理,便于在界面中直接使用。 - 添加手势识别器:
UIPinchGestureRecognizer处理捏合缩放,限制缩放范围在0.5到3.0之间,避免过度缩放。UIRotationGestureRecognizer处理旋转操作,支持用户调整篮球场的方向以适配真实场地。
- 绘制时应用变换:在
draw(_:)方法中,先将坐标系移到视图中心,依次应用旋转和缩放变换,再调用原有的绘制方法,确保变换正确作用于整个篮球场。 - 优化原有绘制代码:补充了原代码中缺失的基础边界路径(如
bezier8Path),确保篮球场有完整的灰色填充背景和黑色边框。
使用方式
在ViewController中直接创建并添加该视图:
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let courtView = BasketballCourtView(frame: CGRect(x: 50, y: 100, width: 300, height: 150)) view.addSubview(courtView) } }
内容的提问来源于stack exchange,提问作者Sharukh Khan
相关产品推荐
相关产品推荐

