Нарисуйте концентрический круг, используя UIBezierPath - PullRequest
1 голос
/ 03 ноября 2019

Я пытаюсь нарисовать концентрический круг, используя 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 ]

2 голосов
/ 03 ноября 2019

Вот как именно это сделать, используя слои.

Действительно, есть много преимуществ, если использовать слои, а не рисовать (т. Е. Рисовать их в draw#rect). Начать с анимации проще, ее можно использовать многократно, и вам не нужно беспокоиться о сложности «если вид переместится / изменится, что именно я должен перерисовать». Все автоматически со слоями.

1. Как всегда, сделайте слои и установите их рамки в layoutSubviews.

В iOS, когда вы создаете слои, вы (A) делаете их (почти наверняка с ленивым 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) делает слои (почти наверняка с ленивым var) и (B) на макетераз, конечно, вы устанавливаете размер кадра.

2. Установите все «фиксированные» аспекты слоев, когда они сделаны:

Какие качества слоев не будут никогда не изменятся ?

Установите эти «неизменные» качества в ленивом var:

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.

Во-вторых, относительно внутреннего объекта BLOB. Обратите внимание, что он просто меньше, чем общая единица. Другими словами, рамка внутреннего большого двоичного объекта должна быть уменьшена на некоторую величину.

Для достижения этого используйте критический вызов inset(by: UIEdgeInsets в iOS. Используйте это часто!

Вы используете inset(by: UIEdgeInsets очень часто в iOS .

Вот важный вспомогательный совет. Я действительно призываю вас использовать 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, поэтому вам не нужно дурачиться с дугами и Пи!

Так что это так просто ...

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 вы неожиданно задаете фактический путь слоя формы, в макете Subviews .

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
    }
}

Следующие шаги:

Если вы хотите взять на себя изображение в середине: ссылка

enter image description here

Если вы хотите, eek, взять тени: ссылка

Глядя на градиенты? ссылка

Частичные дуги: ссылка

2 голосов
/ 03 ноября 2019

Вам не нужны слои для этого. Просто используйте 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:).

...