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新增绘制圆内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • IOS开发之为视图绘制单(多)个圆角实例代码

    IOS开发之为视图绘制单(多)个圆角实例代码 前言: 为视图绘制圆角,圆角可以选左上角.左下角.右下角.右上角.全部圆角 //Core Raduias UIView *actionView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)]; UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:actionView.bounds byRoundingCo

  • iOS中修改UISearchBar圆角的小技巧分享

    前言 在我们日常开发中,经常会遇到一些需求非要把 UISearchBar 默认的圆角矩形的圆角改大,顶端改成圆形的.虽然系统没有提供这个 API,不过还是有一个简单方法可以解决. 解决方法: 首先在 UIView 的 category 里加一个方法: UIView+Utils.m - (UIView*)subViewOfClassName:(NSString*)className { for (UIView* subView in self.subviews) { if ([NSStringFr

  • iOS如何裁剪圆形头像

    本文实例为大家介绍了iOS裁剪圆形头像的详细代码,供大家参考,具体内容如下 - (void)viewDidLoad { [super viewDidLoad]; //加载图片 UIImage *image = [UIImage imageNamed:@"菲哥"]; //获取图片尺寸 CGSize size = image.size; //开启位图上下文 UIGraphicsBeginImageContextWithOptions(size, NO, 0); //创建圆形路径 UIBez

  • iOS渐变圆环旋转动画CAShapeLayer CAGradientLayer

    iOS渐变圆环旋转动画CAShapeLayer CAGradientLayer shape.gif demo.png - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. CALayer *layer = [CALayer layer]; layer.backgroundColor = [UIColor redColor

  • IOS实现圆形图片效果的两种方法

    先来看看效果图 ↓ 这个显示效果的做法有很多: 方法一: 使用两张图片, 作为边框的背景图片和中间的图片,然后使用imageView的cornerRadius来做圆, 具体代码如下: backImageView.layer.cornerRadius = backImageView.frame.size.width / 2; backImageView.layer.masksToBounds = YES; frontImageView.layer.cornerRadius = frontImage

  • IOS设置按钮为圆角的示例代码

    iOS中很多时候都需要用到指定风格的圆角按钮,以下是UIButton提供的创建圆角按钮方法 设置按钮的4个角: 左上:UIRectCornerTopLeft 左下:UIRectCornerBottomLeft 右上:UIRectCornerTopRight 右下:UIRectCornerBottomRight 示例代码: UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(50, 60, 80, 40)]; button.b

  • iOS实现圆角箭头矩形的提示框

    先来看看我们见过的一些圆角箭头矩形的提示框效果图 一.了解CGContextRef 首先需要对 CGContextRef 了解, 作者有机会再进行下详细讲解, 这篇中简单介绍下, 方便后文阅读理解. 先了解 CGContextRef 坐标系 坐标系 举例说明 : 对于 商城类App 有很多原价, 现价对比 .那 原件的横线怎么画, 就可以用CGContextRef - (void)drawRect:(CGRect)rect { // Drawing code [super drawRect:re

  • iOS实现带文字的圆形头像效果

    下面就来实现一下这种效果   圆形头像的绘制 先来看一下效果图 分析一下: 1.首先是需要画带有背景色的圆形头像 2.然后是需要画文字 3.文字是截取的字符串的一部分 4.不同的字符串,圆形的背景色是不一样的 5.对于中英文同样处理,英文的一个字符和中文的一个汉字同样算作一个字符 6.文字总是居中显示 好 有了这样几点 我们就可以开始画图了 看一下最终实现的效果图 首先 ,我们需要自定义一个view当做自定义头像,在view的drawRect方法中进行图像的绘制 @interface Round

  • 详解iOS 裁剪圆形图像并显示(类似于微信头像)

    本文主要讲解如何从照片库选择一张照片后将其裁剪成圆形头像并显示,类似于微信头像那种模式. 本文的方法也适用于当时拍照获取的图像,方法类似,所以不再赘述. 本文主要是在iOS 10环境下使用,此时如果要使用使用系统照片库.照相机等功能需要授权,授权方法如下: 右键点击工程目录中的"Info.plist文件-->Open As -->Source Code",打开复制以下你在应用中使用的隐私权限设置(描述自己修改): <key>NSVideoSubscriberAc

  • IOS 圆球沿着椭圆轨迹做动画

    前言:最近公司项目有个需求,需要实现让一个view沿着椭圆轨迹做动画,效果实现后,就自己封装做了一个小demo,使用更方便.先看效果: 椭圆.gif 效果图中的白色椭圆轨迹线其实是用贝塞尔曲线画出来的,为了清晰的看出来运动的轨迹.其实项目中是不显示轨迹线的,也就是小球是悬空运动的.若不需要删除掉即可. 实现步骤: 1.首先设定关键帧动画CAKeyframeAnimation的一些属性,比如运动时间和重复次数和calculationMode模式,我们选择kCAAnimationPaced 使得动画

随机推荐