這篇文章主要為大家展示了iOS如何新增繪制圓,內(nèi)容簡(jiǎn)而易懂,希望大家可以學(xué)習(xí)一下,學(xué)習(xí)完之后肯定會(huì)有收獲的,下面讓小編帶大家一起來(lái)看看吧。
目前創(chuàng)新互聯(lián)建站已為上千多家的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁(yè)空間、網(wǎng)站托管、服務(wù)器托管、企業(yè)網(wǎng)站設(shè)計(jì)、迪慶州網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。
iOS 的坐標(biāo)系和我們幾何課本中的二維坐標(biāo)系并不一樣!
# BezierPath繪制圓弧
使用 UIBezierPath 進(jìn)行繪制圓弧的方法,通常會(huì)直接使用 addArc :
addArc(withCenter:, radius:, startAngle:, endAngle:, clockwise:)
或者使用 addCurve 進(jìn)行擬圓?。?/p>
addCurve(to:, controlPoint1:, controlPoint2:)
其實(shí)我們可以通過(guò),兩個(gè)坐標(biāo)點(diǎn)(startPoint & endPoint),和兩點(diǎn)間的線段對(duì)應(yīng)的圓弧的弧度(angle/radian)就能確定這個(gè)圓的信息(半徑radius, center), 所以我們是不是可以封裝出只提供 start, end 和 angle 就能繪制 arc 的函數(shù)?
addArc(startPoint: , endPoint: , angle: , clockwise:)
# 計(jì)算兩點(diǎn)間的距離
這里邏輯很簡(jiǎn)單不做贅述。
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) }
# 計(jì)算兩點(diǎn)間的夾角
計(jì)算 point 和 origin 連線在 iOS 坐標(biāo)系的角度
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: 修正標(biāo)準(zhǔn)坐標(biāo)系角度到 iOS 坐標(biāo)系 let rotationAdjustment = Double.pi * 0.5 let offsetX = point.x - origin.x let offsetY = point.y - origin.y // Note: 使用 -offsetY 是因?yàn)?iOS 坐標(biāo)系與標(biāo)準(zhǔn)坐標(biāo)系的區(qū)別 if offsetY > 0 { return Double(atan(offsetX / -offsetY)) + rotationAdjustment } else { return Double(atan(offsetX / -offsetY)) - rotationAdjustment } }
# 計(jì)算圓心的坐標(biāo)
如果你已經(jīng)將幾何知識(shí)丟的差不多了的話,我在這里畫了個(gè)大概的草圖,如下( angle 比較小時(shí)):
angle 比較大時(shí):
所以我么可以寫出如下計(jì)算中心點(diǎn)的代碼
// Woring: 只計(jì)算從start到end **順時(shí)針** 計(jì)算對(duì)應(yīng)的 **小于π** 圓弧對(duì)應(yīng)的圓心 // Note: 計(jì)算逆時(shí)針(end到start)可以看做將傳入的start和end對(duì)調(diào)后計(jì)算順時(shí)針時(shí)的圓心位置 // Note: 計(jì)算大于π的叫相當(dāng)于將end和start對(duì)換后計(jì)算2π-angle的順時(shí)針圓心位置 // 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) } // 普通情況 // 計(jì)算半徑 let radius = lineAB * 0.5 / CGFloat(sin(radian * 0.5)) // 計(jì)算與 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 為原點(diǎn)判斷象限區(qū)間(iOS坐標(biāo)系) 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) }
這里附上一個(gè)逆時(shí)針繪制第一張圖中圓心位置的草圖,圖中已將 start 和 end 對(duì)換
如果你對(duì)其中計(jì)算時(shí)到底該使用 + 還是 - 有困惑的話也可以自己多畫些草圖大概驗(yàn)證下,總之有疑惑多動(dòng)手🤭
# 實(shí)現(xiàn)我們的目標(biāo)函數(shù)
在有了計(jì)算圓心位置,和兩點(diǎn)間角度的函數(shù)后我們很容易就能實(shí)現(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: 保證計(jì)算圓心時(shí)是從 start 到 end 順時(shí)針 小于 π 的角 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: 逆時(shí)針繪制則交換 startAngle 和 endAngle,并且將開(kāi)始點(diǎn)移動(dòng)的 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) }
# 完結(jié)
最后也不知道是你否會(huì)碰到相同的需求,這里附上源碼和一份樣例及運(yùn)行結(jié)果圖;
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 提供的接口, 使用 度數(shù)(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)
以上就是關(guān)于iOS如何新增繪制圓的內(nèi)容,如果你們有學(xué)習(xí)到知識(shí)或者技能,可以把它分享出去讓更多的人看到。