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

时间: 2020-05-16

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设置按钮为圆角的示例代码

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

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

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

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

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

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

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

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

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

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

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

iOS如何裁剪圆形头像

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

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

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

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

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

详解iOS Method Swizzling使用陷阱

在阅读团队一项目源码时,发现Method Swizzling的写法有些瑕疵.这篇文章主要就介绍iOS Method Swizzling的正确写法应该是什么样的. 下面是iOS Method Swizzling的一种实现: + (void)load { Class class = [self class]; SEL fromSelector = @selector(func); SEL toSelector = @selector(easeapi_func); Method fromMethod

详解 iOS 系统中的视图动画

动画为用户界面的状态转换提供了流畅的可视化效果, 在 iOS 中大量使用了动画效果, 包括改变视图位置. 大小. 从可视化树中删除视图, 隐藏视图等. 你可以考虑用动画效果给用户提供反馈或者用来实现有趣的特效. 在 iOS 系统中, Core Animation 提供了内置的动画支持, 创建动画不需要任何绘图的代码, 你要做的只是激发指定的动画, 接下来就交给 Core Animation 来渲染, 总之, 复杂的动画只需要几行代码就可以了. 哪些属性可以添加动画效果 根据 iOS 视图编程指南

详解Python+OpenCV实现图像二值化

目录 一.图像二值化 1.效果 2.源码 二.图像二值化(调节阈值) 1.源码一 2.源码二 一.图像二值化 1.效果 2.源码 import cv2 import numpy as np import matplotlib.pyplot as plt # img = cv2.imread('test.jpg') #这几行是对图像进行降噪处理,但事还存在一些问题. # dst = cv2.fastNlMeansDenoisingColored(img,None,10,10,7,21) # plt

详解IOS UITableViewCell 的 imageView大小更改

详解IOS UITableViewCell 的 imageView大小更改 实例代码: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCell

详解IOS开发中生成推送的pem文件

详解IOS开发中生成推送的pem文件 具体步骤如下: 首先,需要一个pem的证书,该证书需要与开发时签名用的一致. 具体生成pem证书方法如下: 1. 登录到 iPhone Developer Connection Portal(http://developer.apple.com/iphone/manage/overview/index.action )并点击 App IDs 2. 创建一个不使用通配符的 App ID .通配符 ID 不能用于推送通知服务.例如,  com.itotem.ip

详解IOS中文件路径判断是文件还是文件夹

详解IOS中文件路径判断是文件还是文件夹 方法1 + (BOOL)isDirectory:(NSString *)filePath { BOOL isDirectory = NO; [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]; return isDirectory; } 方法2 + (BOOL)isDirectory:(NSString *)filePath { NSNum

详解IOS串行队列与并行队列进行同步或者异步的实例

详解IOS串行队列与并行队列进行同步或者异步的实例 IOS中GCD的队列分为串行队列和并行队列,任务分为同步任务和异步任务,他们的排列组合有四种情况,下面分析这四种情况的工作方式. 同步任务,使用GCD dispatch_sync 进行派发任务 - (void)testSync { dispatch_queue_t serialQueue = dispatch_queue_create("com.zyt.queue", DISPATCH_QUEUE_SERIAL); dispatch_

详解IOS 单例的两种方式

详解IOS 单例的两种方式 方法一: #pragma mark - #pragma mark sharedSingleton methods //单例函数 static RtDataModel *sharedSingletonManager = nil; + (RtDataModel *)sharedManager { @synchronized(self) { if (sharedSingletonManager == nil) { sharedSingletonManager = [[sel

详解 IOS下int long longlong的取值范围

详解 IOS下int long longlong的取值范围 32bit下: unsigned int 0-4294967295 int -2147483648-2147483647 unsigned long 和int一样 long 和int一样 long long的最大值:9223372036854775807 long long的最小值:-9223372036854775808 unsigned long long的最大值:1844674407370955161 __int64的最大值:92