Рисование UIBezierPath сгенерированного кода UIView

У меня есть UIView, добавленный в код во время выполнения.

Я хочу нарисовать a UIBezierPath в нем, но это означает, что я должен переопределить drawRect для UIView?

Или есть ли другой способ рисования на пользовательском UIView?

Вот код для генерации UIView:

UIView* shapeView = [[UIView alloc]initWithFrame:CGRectMake(xOrigin,yOrigin+(i*MENU_BLOCK_FRAME_HEIGHT), self.shapeScroll.frame.size.width, MENU_BLOCK_FRAME_HEIGHT)];
shapeView.clipsToBounds = YES;

И вот функция для создания и возврата UIBezierPath:

- (UIBezierPath*)createPath
{
    UIBezierPath* path = [[UIBezierPath alloc]init];
    [path moveToPoint:CGPointMake(100.0, 50.0)];
    [path addLineToPoint:CGPointMake(200.0,50.0)];
    [path addLineToPoint:CGPointMake(200.0, 200.0)];
    [path addLineToPoint:CGPointMake(100.0, 200.0)];
    [path closePath];
    return path;
}

Ответ 1

Не так давно я даже не знал, как произносить Безье, не говоря уже о том, как использовать пути Безье для создания произвольной формы. Вот что я узнал. Оказывается, они не так страшны, как кажется на первый взгляд.

Как нарисовать путь Безье в пользовательском представлении

Это основные шаги:

  1. Создайте контур желаемой формы.
  2. Разделите контурный контур на сегменты линий, дуг и кривых.
  3. Создайте этот путь программно.
  4. Нарисуйте путь либо в drawRect либо используя CAShapeLayer.

Контур формы дизайна

Вы можете сделать что угодно, но в качестве примера я выбрал форму ниже. Это может быть всплывающая клавиша на клавиатуре.

enter image description here

Разделите путь на сегменты

Посмотрите на свой дизайн фигуры и разбейте его на более простые элементы линий (для прямых линий), дуг (для кругов и круглых углов) и кривых (для всего остального).

Вот как будет выглядеть наш пример дизайна:

enter image description here

  • Черные - это отрезки
  • Светло-голубые - дуговые сегменты
  • Красные кривые
  • Оранжевые точки - это контрольные точки для кривых
  • Зеленые точки - это точки между отрезками пути
  • Пунктирные линии показывают ограничивающий прямоугольник
  • Синие цифры - это сегменты в том порядке, в котором они будут добавлены программно

Построить путь программно

Мы произвольно начнем в левом нижнем углу и будем работать по часовой стрелке. Я буду использовать сетку на изображении, чтобы получить значения x и y для точек. Я здесь жестко закодирую все, но, конечно, вы не сделаете этого в реальном проекте.

Основной процесс:

  1. Создайте новый UIBezierPath
  2. Выберите начальную точку на пути с помощью moveToPoint
  3. Добавить сегменты к пути
    • строка: addLineToPoint
    • arc: addArcWithCenter
    • кривая: addCurveToPoint
  4. Закройте путь с помощью closePath

Вот код для создания пути на изображении выше.

func createBezierPath() -> UIBezierPath {

    // create a new path
    let path = UIBezierPath()

    // starting point for the path (bottom left)
    path.move(to: CGPoint(x: 2, y: 26))

    // *********************
    // ***** Left side *****
    // *********************

    // segment 1: line
    path.addLine(to: CGPoint(x: 2, y: 15))

    // segment 2: curve
    path.addCurve(to: CGPoint(x: 0, y: 12), // ending point
        controlPoint1: CGPoint(x: 2, y: 14),
        controlPoint2: CGPoint(x: 0, y: 14))

    // segment 3: line
    path.addLine(to: CGPoint(x: 0, y: 2))

    // *********************
    // ****** Top side *****
    // *********************

    // segment 4: arc
    path.addArc(withCenter: CGPoint(x: 2, y: 2), // center point of circle
        radius: 2, // this will make it meet our path line
        startAngle: CGFloat(M_PI), // π radians = 180 degrees = straight left
        endAngle: CGFloat(3*M_PI_2), // 3π/2 radians = 270 degrees = straight up
        clockwise: true) // startAngle to endAngle goes in a clockwise direction

    // segment 5: line
    path.addLine(to: CGPoint(x: 8, y: 0))

    // segment 6: arc
    path.addArc(withCenter: CGPoint(x: 8, y: 2),
                          radius: 2,
                          startAngle: CGFloat(3*M_PI_2), // straight up
        endAngle: CGFloat(0), // 0 radians = straight right
        clockwise: true)

    // *********************
    // ***** Right side ****
    // *********************

    // segment 7: line
    path.addLine(to: CGPoint(x: 10, y: 12))

    // segment 8: curve
    path.addCurve(to: CGPoint(x: 8, y: 15), // ending point
        controlPoint1: CGPoint(x: 10, y: 14),
        controlPoint2: CGPoint(x: 8, y: 14))

    // segment 9: line
    path.addLine(to: CGPoint(x: 8, y: 26))

    // *********************
    // **** Bottom side ****
    // *********************

    // segment 10: line
    path.close() // draws the final line to close the path

    return path
}

Примечание. Часть приведенного выше кода можно уменьшить, добавив в одну команду строку и дугу (поскольку у дуги есть подразумеваемая начальная точка). Смотрите здесь для более подробной информации.

Нарисуй путь

Мы можем нарисовать путь либо в слое, либо в drawRect.

Метод 1: Нарисуйте путь в слое

Наш пользовательский класс выглядит следующим образом. Мы добавляем наш путь Безье к новому CAShapeLayer когда представление инициализируется.

import UIKit
class MyCustomView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    func setup() {

        // Create a CAShapeLayer
        let shapeLayer = CAShapeLayer()

        // The Bezier path that we made needs to be converted to 
        // a CGPath before it can be used on a layer.
        shapeLayer.path = createBezierPath().cgPath

        // apply other properties related to the path
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.fillColor = UIColor.white.cgColor
        shapeLayer.lineWidth = 1.0
        shapeLayer.position = CGPoint(x: 10, y: 10)

        // add the new layer to our custom view
        self.layer.addSublayer(shapeLayer)
    }

    func createBezierPath() -> UIBezierPath {

        // see previous code for creating the Bezier path
    }
}

И создание нашего представления в контроллере вида, как это

override func viewDidLoad() {
    super.viewDidLoad()

    // create a new UIView and add it to the view controller
    let myView = MyCustomView()
    myView.frame = CGRect(x: 100, y: 100, width: 50, height: 50)
    myView.backgroundColor = UIColor.yellow
    view.addSubview(myView)

}

Мы получаем...

enter image description here

Хм, это немного мало, потому что я жестко закодировал все числа в. Я могу увеличить размер пути, хотя, как это:

let path = createBezierPath()
let scale = CGAffineTransform(scaleX: 2, y: 2)
path.apply(scale)
shapeLayer.path = path.cgPath

enter image description here

Способ 2: рисовать путь в draw

Использование draw медленнее, чем рисование на слое, поэтому этот метод не рекомендуется, если он вам не нужен.

Вот пересмотренный код для нашего пользовательского представления:

import UIKit
class MyCustomView: UIView {

    override func draw(_ rect: CGRect) {

        // create path (see previous code)
        let path = createBezierPath()

        // fill
        let fillColor = UIColor.white
        fillColor.setFill()

        // stroke
        path.lineWidth = 1.0
        let strokeColor = UIColor.blue
        strokeColor.setStroke()

        // Move the path to a new location
        path.apply(CGAffineTransform(translationX: 10, y: 10))

        // fill and stroke the path (always do these last)
        path.fill()
        path.stroke()

    }

    func createBezierPath() -> UIBezierPath {

        // see previous code for creating the Bezier path
    }
}

что дает нам тот же результат...

enter image description here

Дальнейшее обучение

Я очень рекомендую посмотреть на следующие материалы. Это то, что, наконец, сделало пути Безье понятными для меня. (И научил меня, как это произносить:/ˈbɛ zi eɪ/.)

Ответ 2

Было бы проще, если бы вы использовали CAShapeLayer, например:

CAShapeLayer *shapeView = [[CAShapeLayer alloc] init];

И установите его path:

[shapeView setPath:[self createPath].CGPath];

Наконец добавьте его:

[[self.view layer] addSublayer:shapeView];

Ответ 3

Вы можете использовать CAShapeLayer для этого.

Как это...

CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [self createPath].CGPath;
shapeLayer.strokeColor = [UIColor redColor].CGColor; //etc...
shapeLayer.lineWidth = 2.0; //etc...
shapeLayer.position = CGPointMake(100, 100); //etc...
[self.layer addSublayer:shapeLayer];

Затем будет добавлен и нарисован путь без переопределения drawRect.

Ответ 4

Существует несколько способов достижения желаемого. Я видел больше всего: переопределить drawRect, нарисовать фигуру в CAShapeLayer, а затем добавить ее в качестве подуровня к вашему представлению, или нарисуйте свой путь в другом контексте, сохраните это как изображение, а затем добавьте его в свой вид.

Все это разумный выбор, и какой из них лучше всего зависит от многих других факторов, таких как вы будете постоянно добавлять фигуры, как часто это называется и т.д.

Ответ 5

Как отмечали другие плакаты, использование слоя формы - хороший способ пойти.

Уровни формы a, скорее всего, дадут вам лучшую производительность, чем переопределение drawRect.

Если вы хотите сами нарисовать свой путь, тогда да, вы должны переопределить drawRect для своего настраиваемого класса вида.

Ответ 6

Да, вы должны переопределить drawrect, если хотите нарисовать что-либо. Создание UIBezierPath может быть выполнено в любом месте, но чтобы сделать что-то, что вам нужно сделать внутри метода drawrect

Вы должны называть setNeedsDisplay, если вы переопределите drawRect в подклассе UIView, который в основном представляет собой пользовательский вид, который рисует что-то на экране, например строки, изображение, прямоугольник.