1. 程式人生 > 程式設計 >iOS新增繪製圓的方法例項程式碼

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 比較小時):

iOS新增繪製圓的方法例項程式碼

angle 比較大時:

iOS新增繪製圓的方法例項程式碼

所以我麼可以寫出如下計算中心點的程式碼

// 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 對換

iOS新增繪製圓的方法例項程式碼

如果你對其中計算時到底該使用 + 還是 - 有困惑的話也可以自己多畫些草圖大概驗證下,總之有疑惑多動手🤭

# 實現我們的目標函式

在有了計算圓心位置,和兩點間角度的函式後我們很容易就能實現 addArc(startPoint:,clockwise:) 了;

func addArc(startPoint start: 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,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,angle: Double.pi * 0.8,clockwise: true)
  
  start = CGPoint(x: 142,y: 130)
  end = CGPoint(x: 162,angle: Double.pi * 0.4,clockwise: true)
  
  start = CGPoint(x: 140,y: 130)
  end = CGPoint(x: 160,clockwise: false)
  path.move(to: start)
  path.addArc(startPoint: start,clockwise: false)
  
  path.close()
  path.lineWidth = 1
  UIColor.red.setStroke()
  path.stroke()
}

iOS新增繪製圓的方法例項程式碼

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,angle: Angle,clockwise: Bool)

感謝閱讀,祝好祝順🥰

總結

到此這篇關於iOS新增繪製圓的文章就介紹到這了,更多相關iOS新增繪製圓內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!