iOS新增绘制圆的方法实例代码

    iOS 的坐标系和我们几何课本中的二维坐标系并不一样!

    # BezierPath绘制圆弧

    使用 UIBezierPath 进行绘制圆弧的方法,通常会直接使用 addArc :

    
    addArc(withCenter:, radius:, startAngle:, endAngle:, clockwise:)

    或者使用 addCurve 进行拟圆弧:

    
    addCurve(to:, controlPoint1:, controlPoint2:)

    其实我们可以通过,两个坐标点(startPoint & endPoint),和两点间的线段对应的圆弧的弧度(angle/radian)就能确定这个圆的信息(半径radius, center), 所以我们是不是可以封装出只提供 start, end 和 angle 就能绘制 arc 的函数?

    
    addArc(startPoint: , endPoint: , angle: , clockwise:)

    # 计算两点间的距离

    这里逻辑很简单不做赘述。

    
    func calculateLineLength(_ point1: CGPoint, _ point2: CGPoint) -> CGFloat {
      let w = point1.x - point2.x
      let h = point1.y - point2.y
      return sqrt(w * w + h * h)
    }

    # 计算两点间的夹角

    计算 point 和 origin 连线在 iOS 坐标系的角度

    
    func calculateAngle(point: CGPoint, origin: CGPoint) -> Double {
      
      if point.y == origin.y {
        return point.x > origin.x ? 0.0 : -Double.pi
      }
      
      if point.x == origin.x {
        return point.y > origin.y ? Double.pi * 0.5 : Double.pi * -0.5
      }
      // Note: 修正标准坐标系角度到 iOS 坐标系
      let rotationAdjustment = Double.pi * 0.5
      
      let offsetX = point.x - origin.x
      let offsetY = point.y - origin.y
      // Note: 使用 -offsetY 是因为 iOS 坐标系与标准坐标系的区别
      if offsetY > 0 {
        return Double(atan(offsetX / -offsetY)) + rotationAdjustment
      } else {
        return Double(atan(offsetX / -offsetY)) - rotationAdjustment
      }
    }

    # 计算圆心的坐标

    如果你已经将几何知识丢的差不多了的话,我在这里画了个大概的草图,如下( angle 比较小时):

    angle 比较大时:

    所以我么可以写出如下计算中心点的代码

    
    // Woring: 只计算从start到end **顺时针** 计算对应的 **小于π** 圆弧对应的圆心
    // Note: 计算逆时针(end到start)可以看做将传入的start和end对调后计算顺时针时的圆心位置
    // Note: 计算大于π的叫相当于将end和start对换后计算2π-angle的顺时针圆心位置
    // Note: 综上传入start,end,angle 右外部自行处理逻辑
    func calculateCenterFor(startPoint start: CGPoint, endPoint end: CGPoint, radian: Double) -> CGPoint {
      guard radian <= Double.pi else {
        fatalError("Does not support radian calculations greater than π!")
      }
      
      guard start != end else {
        fatalError("Start position and end position cannot be equal!")
      }
      
      if radian == Double.pi {
        let centerX = (end.x - start.x) * 0.5 + start.x
        let centerY = (end.y - start.y) * 0.5 + start.y
        return CGPoint(x: centerX, y: centerY)
      }
      
      let lineAB = calculateLineLength(start, end)
      
      // 平行 Y 轴
      if start.x == end.x {
        let centerY = (end.y - start.y) * 0.5 + start.y
        let tanResult = CGFloat(tan(radian * 0.5))
        let offsetX = lineAB * 0.5 / tanResult
        let centerX = start.x + offsetX * (start.y > end.y ? 1.0 : -1.0)
        return CGPoint(x: centerX, y: centerY)
      }
      
      // 平行 X 轴
      if start.y == end.y {
        let centerX = (end.x - start.x) * 0.5 + start.x
        let tanResult = CGFloat(tan(radian * 0.5))
        let offsetY = lineAB * 0.5 / tanResult
        let centerY = start.y + offsetY * (start.x < end.x ? 1.0 : -1.0)
        return CGPoint(x: centerX, y: centerY)
      }
      
      // 普通情况
      
      // 计算半径
      let radius = lineAB * 0.5 / CGFloat(sin(radian * 0.5))
      // 计算与 Y 轴的夹角
      let angleToYAxis = atan(abs(start.x - end.x) / abs(start.y - end.y))
      let cacluteAngle = CGFloat(Double.pi - radian) * 0.5 - angleToYAxis
      // 偏移量
      let offsetX = radius * sin(cacluteAngle)
      let offsetY = radius * cos(cacluteAngle)
      
      var centetX = end.x
      var centerY = end.y
      // 以 start 为原点判断象限区间(iOS坐标系)
      if end.x > start.x && end.y < start.y {
        // 第一象限
        centetX = end.x + offsetX
        centerY = end.y + offsetY
      } else if end.x > start.x && end.y > start.y {
        // 第二象限
        centetX = start.x - offsetX
        centerY = start.y + offsetY
      } else if end.x < start.x && end.y > start.y {
        // 第三象限
        centetX = end.x - offsetX
        centerY = end.y - offsetY
      } else if end.x < start.x && end.y < start.y {
        // 第四象限
        centetX = start.x + offsetX
        centerY = start.y - offsetY
      }
      
      return CGPoint(x: centetX, y: centerY)
    }

    这里附上一个逆时针绘制第一张图中圆心位置的草图,图中已将 start 和 end 对换

    如果你对其中计算时到底该使用 + 还是 – 有困惑的话也可以自己多画些草图大概验证下,总之有疑惑多动手🤭

    # 实现我们的目标函数

    在有了计算圆心位置,和两点间角度的函数后我们很容易就能实现 addArc(startPoint: , endPoint: , angle: , clockwise:) 了;

    
    func addArc(startPoint start: CGPoint, endPoint end: CGPoint, angle: Double, clockwise: Bool) {
      
      guard start != end && (angle >= 0 && angle <= 2 * Double.pi) else {
        return
      }
      if angle == 0 {
        move(to: start)
        addLine(to: end)
        return
      }
      
      var tmpStart = start, tmpEnd = end, tmpAngle = angle
      // Note: 保证计算圆心时是从 start 到 end 顺时针 小于 π 的角
      if tmpAngle > Double.pi {
        tmpAngle = 2 * Double.pi - tmpAngle
        (tmpStart, tmpEnd) = (tmpEnd, tmpStart)
      }
      if !clockwise {
        (tmpStart, tmpEnd) = (tmpEnd, tmpStart)
      }
      
      let center = calculateCenterFor(startPoint: tmpStart, endPoint: tmpEnd, radian: tmpAngle)
      let radius = calculateLineLength(start, center)
      
      var startAngle = calculateAngle(point: start, origin: center)
      var endAngle = calculateAngle(point: end, origin: center)
      // Note: 逆时针绘制则交换 startAngle 和 endAngle,并且将开始点移动的 end 位置
      if !clockwise {
        (startAngle, endAngle) = (endAngle, startAngle)
        move(to: end)
      }
      
      addArc(withCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: true)
      move(to: end)
    }

    # 完结

    最后也不知道是你否会碰到相同的需求,这里附上源码和一份样例及运行结果图;

    
    override func draw(_ rect: CGRect) {
      
      let path = UIBezierPath()
      var start = CGPoint(x: 160, y: 130)
      var end = CGPoint(x: 180, y: 200)
      path.move(to: start)
      path.addArc(startPoint: start, endPoint: end, angle: Double.pi * 1.6, clockwise: true)
      path.move(to: start)
      path.addArc(startPoint: start, endPoint: end, angle: Double.pi * 0.8, clockwise: true)
      
      start = CGPoint(x: 142, y: 130)
      end = CGPoint(x: 162, y: 200)
      path.move(to: start)
      path.addArc(startPoint: start, endPoint: end, angle: Double.pi * 0.4, clockwise: true)
      
      start = CGPoint(x: 140, y: 130)
      end = CGPoint(x: 160, y: 200)
      path.move(to: start)
      path.addArc(startPoint: start, endPoint: end, angle: Double.pi * 1.6, clockwise: false)
      path.move(to: start)
      path.addArc(startPoint: start, endPoint: end, angle: Double.pi * 0.8, clockwise: false)
      
      path.close()
      path.lineWidth = 1
      UIColor.red.setStroke()
      path.stroke()
    }

    ps: 每次都写 Double.pi / x 很烦? 试试类似于 SwiftUI 提供的接口, 使用 度数(degress) 而非 弧度(radian)

    
    struct Angle {
      private var degress: Double
      static func deggess(_ degress: Double) -> Angle {
        return .init(degress: degress)
      }
      // 弧度
      var radians: Double { Double.pi * degress / 180.0 }
    }
    
    // Angle.deggess(90).radians // 1.570796326794897
    
    func addArc(startPoint start: CGPoint, endPoint end: CGPoint, angle: Angle, clockwise: Bool)

    感谢阅读,祝好祝顺🥰

    总结

    到此这篇关于iOS新增绘制圆的文章就介绍到这了,更多相关iOS新增绘制圆内容请搜索lingkb以前的文章或继续浏览下面的相关文章希望大家以后多多支持lingkb!