You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为自定义篮球场形状添加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
        }
    }
}

关键修改说明

  1. 封装为UIView子类:创建BasketballCourtView继承自UIView,直接承载绘制逻辑和手势处理,便于在界面中直接使用。
  2. 添加手势识别器:
    • UIPinchGestureRecognizer处理捏合缩放,限制缩放范围在0.5到3.0之间,避免过度缩放。
    • UIRotationGestureRecognizer处理旋转操作,支持用户调整篮球场的方向以适配真实场地。
  3. 绘制时应用变换:在draw(_:)方法中,先将坐标系移到视图中心,依次应用旋转和缩放变换,再调用原有的绘制方法,确保变换正确作用于整个篮球场。
  4. 优化原有绘制代码:补充了原代码中缺失的基础边界路径(如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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 00:57:04