使用UIBezierPath绘制同心圆

时间:2019-11-03 12:51:25

标签: ios swift calayer

我正在尝试使用UIBezierPath绘制同心圆,如image所示。

但是我实际上得到的却与this不同。

我试图制作两个不同直径的圆形并填充较小的圆形。

  let path = UIBezierPath(ovalIn: CGRect(x: position.x - diameter / 2, y: position.y - diameter / 2, width: diameter, height: diameter))
  let path2 = UIBezierPath(ovalIn: CGRect(x: position.x - diameter / 2 + 2, y: position.y - diameter / 2 + 2, width: diameter - 2, height: diameter - 2))


        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath
        shapeLayer.strokeColor = color.cgColor
        shapeLayer.fillColor =  UIColor.white.cgColor
        shapeLayer.lineWidth = lineWidth

        let shapeLayer2 = CAShapeLayer()
        shapeLayer.path = path2.cgPath
        shapeLayer.strokeColor = color.cgColor
        shapeLayer.fillColor = isFilled ? color.cgColor : UIColor.white.cgColor
        shapeLayer.lineWidth = lineWidth


        view.layer.addSublayer(shapeLayer)
        view.layer.addSublayer(shapeLayer2)

2 个答案:

答案 0 :(得分:2)

您不需要为此的图层。只需使用UIBezierPath s:

override func draw(_ rect: CGRect) {
    // this is the line width of the outer circle
    let outerLineWidth = CGFloat(3) // adjust this however you like

    // if you didn't create a separate view for these concentric circles,
    // which I strongly recommend you do, replace "bounds" with the desired frame
    let outerCircle = UIBezierPath(ovalIn: bounds.insetBy(dx: outerLineWidth / 2, dy: outerLineWidth / 2))
    UIColor.white.setStroke()
    outerCircle.lineWidth = outerLineWidth
    outerCircle.stroke()

    // this is the distance between the outer and inner circle
    let inset = CGFloat(5); // you can adjust this too
    let innerCircle = UIBezierPath(ovalIn: bounds.insetBy(dx: inset, dy: inset))
    UIColor.white.setFill()
    innerCircle.fill()
}

如您所见,您无需手动计算帧,只需使用insetBy(dx:dy:)方法即可。

答案 1 :(得分:2)

这是使用图层的确切方法。

使用图层而不是绘制图层确实有很多优点。

(通过“绘制”,我的意思是在draw#rect中绘制它。)

从动画开始比较容易;而且更可重用;而且您不必担心“如果视图移动/重塑我应该重画什么”的困难。一切都是自动的。

因此,在制作图层时,这是四个主要工作:

1。与往常一样,制作图层,然后在layoutSubviews中设置其框架。

在iOS中,当您制作图层时,您(A)制作(几乎可以肯定是 lazy var )和(B)在布局时 >,当然,您设置框架的大小

这是在iOS中使用图层的两个基本步骤。

  

请注意,您不要在布局时制作它们,并且不要在开机时设置边框。您只能在启动时制作它们,并且只能在布局时设置框架的大小。

我在情节提要中制作了一个UIView,“ Thingy”,并使用约束将其定位为100x100。

我将背景色设为蓝色,以便您可以看到发生了什么。 (您可能希望将其弄清楚。)

enter image description here

它在应用程序中:

enter image description here

我们将需要环的厚度和间隙:

class Thingy: UIView {

    var thicknessOfOuterRing: CGFloat = 20.0 {
        didSet{ setNeedsLayout() }
    }

    var thicknessOfTheGap: CGFloat = 20.0 {
        didSet{ setNeedsLayout() }
    }

要理解的一个关键点是,更改这些值时,您将不得不调整所有内容的大小。对吧?

这意味着您可以按照通常的方式将“ didSet .. setNeedsLayout”添加到这些属性中。

(类似地,如果您想即时更改颜色,则也可以对颜色进行更改。想要“更改”的任何内容,都需要“ didSet .. setNeedsLayout “模式。)

我们将始终需要两个简单的计算,即半厚度和它们的组合脂肪。只需使用计算变量即可,从而大大简化您的代码。

var halfT: CGFloat { return thicknessOfOuterRing / 2.0 }
var both: CGFloat { return thicknessOfOuterRing + thicknessOfTheGap }

好!有两层。环将是一个形状层,但中间的斑点可以只是一个层:

private lazy var outerBand: CAShapeLayer = {
    let l = CAShapeLayer()
    layer.addSublayer(l)
    return l
}()

private lazy var centralBlob: CALayer = {
    let l = CALayer()
    layer.addSublayer(l)
    return l
}()

现在,您必须在布局时设置其框架:

override func layoutSubviews() {
    super.layoutSubviews()

    outerBand.frame = bounds
    centralBlob.frame = bounds
}

这是在iOS中使用图层的基本过程。

(A)在布局时制作图层(几乎可以肯定是带有惰性变量)和(B),当然,您可以设置框架的大小。

2。制作完图层后,设置它们的所有“固定”方面:

从不改变的图层质量是什么?

在惰性变量中设置 那些“不变的”质量:

private lazy var outerBand: CAShapeLayer = {
    let l = CAShapeLayer()
    l.fillColor = UIColor.clear.cgColor
    l.strokeColor = UIColor.black.cgColor
    layer.addSublayer(l)
    return l
}()

private lazy var centralBlob: CALayer = {
    let l = CALayer()
    l.backgroundColor = UIColor.black.cgColor
    layer.addSublayer(l)
    return l
}()

再一次,从不改变的所有内容,将其推送到惰性变量中。

下一步...

3。在布局中设置图层的所有“更改”外观:

关于中央斑点。在iOS中使用正方形CALayer制作硬币形状非常容易,您只需执行以下操作:

someCALayer.cornerRadius = someCALayer.bounds.width / 2.0

请注意,您可能会和某些程序员一样,实际上使用形状图层,然后去制作圆形形状的麻烦等等。但是只使用普通图层并简单地设置cornerRadius即可。

其次是关于内部斑点。请注意,它只是小于整个单位。换句话说,内部斑点的框架必须缩小一定程度。

要实现此目的,请在iOS中使用关键的inset(by: UIEdgeInsets调用。经常使用!

您在iOS中经常使用inset(by: UIEdgeInsets

这是一个重要的小技巧。我真的鼓励您使用inset(by: UIEdgeInsets( ...来写出“顶部,左侧,底部,右侧”

另一种变化insetBy#dx#dy容易引起混乱;尚不清楚您是要加倍数量还是要加倍数量。

我个人建议使用“长格式” inset(by: UIEdgeInsets( ...,因为这样 绝对毫无疑问

所以我们现在完全完成了内点:

override func layoutSubviews() {
    super.layoutSubviews()

    outerBand.frame = bounds
    outerBand.lineWidth = thicknessOfOuterRing

    centralBlob.frame =
      bounds.inset(by: UIEdgeInsets(top: both, left: both, bottom: both, right: both))
    centralBlob.cornerRadius = centralBlob.bounds.width / 2.0
}

enter image description here

最后,这是您问题的核心:

外圈是使用形状层而非普通层制成的。

形状层由路径定义。

这是在iOS中使用形状图层和路径的关键所在的“一个令人惊讶的技巧”:

4。在iOS中,您实际上是在layoutSubviews中创建形状图层的路径。

根据我的经验,这使程序员从其他平台迁移到iOS时感到非常困惑,但这就是失败的原因。

所以让我们创建一个计算变量,为我们创建路径。

请注意,您一定会使用方便的inset(by: UIEdgeInsets( ...通话。

别忘了在调用此方法时,您应该已经正确设置所有涉及的帧。

最后Apple友好地给了我们一个方便的UIBezierPath#ovalIn电话,这样您就不必傻乎乎地用弧线和Pi了!

就这么简单...

var circlePathForCurrentLayout: UIBezierPath {
    var b = bounds
    b = b.inset(by: UIEdgeInsets(top: halfT, left: halfT, bottom: halfT, right: halfT))
    let p = UIBezierPath(ovalIn: b)
    return p
}

然后,正如它用粗体字母表示的那样,在iOS 中,您意外地在layoutSubviews 中设置了形状层的实际路径。

override func layoutSubviews() {
    super.layoutSubviews()

    outerBand.frame = bounds
    outerBand.lineWidth = thicknessOfOuterRing
    outerBand.path = circlePathForCurrentLayout.cgPath

    centralBlob.frame =
      bounds.inset(by: UIEdgeInsets(top: both, left: both, bottom: both, right: both))
    centralBlob.cornerRadius = centralBlob.bounds.width / 2.0
}

enter image description here

回顾:

1。与往常一样,制作图层,然后在layoutSubviews中设置其框架。

2。设置图层的所有“固定”方面,制作完成后

3。在layoutSubviews 中设置图层的所有“变化的”外观。

4。出乎意料的是,在iOS中,您实际上是在layoutSubviews中创建形状图层的路径

这就是整个过程:

//  Thingy.swift
//  Created for SO on 11/3/19.

import UIKit

class Thingy: UIView {

    var thicknessOfOuterRing: CGFloat = 20.0 {
        didSet{ setNeedsLayout() }
    }

    var thicknessOfTheGap: CGFloat = 20.0 {
        didSet{ setNeedsLayout() }
    }

    var halfT: CGFloat { return thicknessOfOuterRing / 2.0 }
    var both: CGFloat { return thicknessOfOuterRing + thicknessOfTheGap }

    var circlePathForCurrentLayout: UIBezierPath {
        var b = bounds
        b = b.inset(by:
          UIEdgeInsets(top: halfT, left: halfT, bottom: halfT, right: halfT))
        let p = UIBezierPath(ovalIn: b)
        return p
    }

    private lazy var outerBand: CAShapeLayer = {
        let l = CAShapeLayer()
        l.fillColor = UIColor.clear.cgColor
        l.strokeColor = UIColor.black.cgColor
        layer.addSublayer(l)
        return l
    }()

    private lazy var centralBlob: CALayer = {
        let l = CALayer()
        l.backgroundColor = UIColor.black.cgColor
        layer.addSublayer(l)
        return l
    }()

    override func layoutSubviews() {
        super.layoutSubviews()

        outerBand.frame = bounds
        outerBand.lineWidth = thicknessOfOuterRing
        outerBand.path = circlePathForCurrentLayout.cgPath

        centralBlob.frame = bounds.inset(by:
          UIEdgeInsets(top: both, left: both, bottom: both, right: both))
        centralBlob.cornerRadius = centralBlob.bounds.width / 2.0
    }
}

下一步:

如果您要挑战在中间放置图片的挑战:link

enter image description here

如果想,请遮掩阴影:link

正在寻找渐变? link

部分弧:link