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 坐標系與標準坐標系的區(qū)別
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 為原點判斷象限區(qū)間(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 對換

如果你對其中計算時到底該使用 + 還是 - 有困惑的話也可以自己多畫些草圖大概驗證下,總之有疑惑多動手🤭
# 實現(xiàn)我們的目標函數
在有了計算圓心位置,和兩點間角度的函數后我們很容易就能實現(xiàn) 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新增繪制圓內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
iOS開發(fā)之使用Storyboard預覽UI在不同屏幕上的運行效果
使用Storyboard做開發(fā)效率非常高,為了防止在團隊中發(fā)生沖突,采取的解決辦法是負責UI開發(fā)的同事最好每人維護一個Storyboard, 公用的組件使用輕量級的xib或者純代碼來實現(xiàn),下面小編就給大家介紹如何使用Storyboard預覽UI在不同屏幕上的運行效果,需要的朋友可以參考下2015-08-08
IOS 開發(fā)之ObjectiveC的變量類型的字符代表
這篇文章主要介紹了IOS 開發(fā)之ObjectiveC的變量類型的字符代表的相關資料,這里舉例說明如何使用Objective的變量類型的字符,幫助大家學習理解這部分內容,需要的朋友可以參考下2017-08-08

