Как сделать мой UIBezierPath анимированным с помощью CAShapeLayer?

Я пытаюсь анимировать UIBezierPath и установил CAShapeLayer, чтобы попытаться это сделать. К сожалению, анимация не работает, и я не уверен, что какой-либо из слоев имеет какое-либо влияние (поскольку код делает то же самое, что и до того, как у меня появились слои).

Вот фактический код - буду рад любой помощи. Draw2D — это реализация UIView, встроенная в UIViewController. Все рисование происходит внутри класса Draw2D. Вызов [_helper createDrawing...] просто заполняет переменную _uipath точками.

Draw2D.h определяет следующие свойства:

#define defaultPointCount ((int) 25)

@property Draw2DHelper *helper;
@property drawingTypes drawingType;
@property int graphPoints;
@property UIBezierPath *uipath;

@property CALayer *animationLayer;
@property CAShapeLayer *pathLayer;

- (void)refreshRect:(CGRect)rect;

ниже фактическая реализация:

//
//  Draw2D.m
//  Draw2D
//
//  Created by Marina on 2/19/13.
//  Copyright (c) 2013 Marina. All rights reserved.
//

#import "Draw2D.h"
#import"Draw2DHelper.h"
#include <stdlib.h>
#import "Foundation/Foundation.h"
#import <QuartzCore/QuartzCore.h>

int MAX_WIDTH;
int MAX_HEIGHT;

@implementation Draw2D

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code

        if (self.pathLayer != nil) {
            [self.pathLayer removeFromSuperlayer];
            self.pathLayer = nil;
        }

        self.animationLayer = [CALayer layer];
        self.animationLayer.frame = self.bounds;
        [self.layer addSublayer:self.animationLayer];

        CAShapeLayer *l_pathLayer = [CAShapeLayer layer];
        l_pathLayer.frame = self.frame;
        l_pathLayer.bounds = self.bounds;
        l_pathLayer.geometryFlipped = YES;
        l_pathLayer.path = _uipath.CGPath;
        l_pathLayer.strokeColor = [[UIColor grayColor] CGColor];
        l_pathLayer.fillColor = nil;
        l_pathLayer.lineWidth = 1.5f;
        l_pathLayer.lineJoin = kCALineJoinBevel;

        [self.animationLayer addSublayer:l_pathLayer];
        self.pathLayer = l_pathLayer;
        [self.layer addSublayer:l_pathLayer];
    }
    return self;
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect :(int) points :(drawingTypes) type //:(Boolean) initial
{
    //CGRect bounds = [[UIScreen mainScreen] bounds];
    CGRect appframe= [[UIScreen mainScreen] applicationFrame];
    CGContextRef context = UIGraphicsGetCurrentContext();
    _helper = [[Draw2DHelper alloc ] initWithBounds :appframe.size.width  :appframe.size.height :type];

    CGPoint startPoint = [_helper generatePoint] ;

    [_uipath moveToPoint:startPoint];
    [_uipath setLineWidth: 1.5];

    CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor);

    CGPoint center = CGPointMake(self.center.y, self.center.x) ;
    [_helper createDrawing :type :_uipath :( (points>0) ? points : defaultPointCount) :center];
    self.pathLayer.path = (__bridge CGPathRef)(_uipath);
    [_uipath stroke];

    [self startAnimation]; 
}

- (void) startAnimation {
    [self.pathLayer removeAllAnimations];

    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    pathAnimation.duration = 3.0;
    pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
    pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
    [self.pathLayer addAnimation:pathAnimation forKey:@"strokeEnd"];

}

- (void)drawRect:(CGRect)rect {
    if (_uipath == NULL)
        _uipath = [[UIBezierPath alloc] init];
    else
        [_uipath removeAllPoints];

    [self drawRect:rect  :self.graphPoints :self.drawingType ];
}

- (void)refreshRect:(CGRect)rect {
    [self setNeedsDisplay];
}

@end

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

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

заранее спасибо.


person marina    schedule 10.03.2013    source источник


Ответы (1)


Похоже, вы пытаетесь анимировать внутри drawRect (по крайней мере, косвенно). Это не совсем имеет смысл. Вы не оживляете внутри drawRect. drawRect используется для рисования одного кадра. Некоторая анимация выполняется с помощью таймеров или CADisplayLink, которые неоднократно вызывают setNeedsDisplay (что заставит iOS вызывать ваш drawRect), во время которого вы можете нарисовать один кадр, показывающий ход анимации в этот момент. Но у вас просто нет drawRect, инициирующего какую-либо анимацию самостоятельно.

Но, поскольку вы используете CAShapeLayer и CABasicAnimation Core Animation, вам вообще не нужен пользовательский drawRect. Quartz Core Animation позаботится обо всем за вас. Например, вот мой код для анимации рисунка UIBezierPath:

#import <QuartzCore/QuartzCore.h>

@interface View ()
@property (nonatomic, weak) CAShapeLayer *pathLayer;
@end

@implementation View

/*
// I'm not doing anything here, so I can comment this out
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}
*/

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
}
*/

// It doesn't matter what my path is. I could make it anything I wanted.

- (UIBezierPath *)samplePath
{
    UIBezierPath *path = [UIBezierPath bezierPath];

    // build the path here

    return path;
}

- (void)startAnimation
{
    if (self.pathLayer == nil)
    {
        CAShapeLayer *shapeLayer = [CAShapeLayer layer];

        shapeLayer.path = [[self samplePath] CGPath];
        shapeLayer.strokeColor = [[UIColor grayColor] CGColor];
        shapeLayer.fillColor = nil;
        shapeLayer.lineWidth = 1.5f;
        shapeLayer.lineJoin = kCALineJoinBevel;

        [self.layer addSublayer:shapeLayer];

        self.pathLayer = shapeLayer;
    }

    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    pathAnimation.duration = 3.0;
    pathAnimation.fromValue = @(0.0f);
    pathAnimation.toValue = @(1.0f);
    [self.pathLayer addAnimation:pathAnimation forKey:@"strokeEnd"];
}

@end

Затем, когда я хочу начать рисовать анимацию, я просто вызываю свой метод startAnimation. Вероятно, мне вообще не нужен подкласс UIView для чего-то такого простого, поскольку я фактически не меняю поведение UIView. Определенно бывают случаи, когда вы создаете подкласс UIView с пользовательской реализацией drawRect, но здесь это не требуется.

Вы просили ссылки:

person Rob    schedule 10.03.2013
comment
Спасибо. Мое приложение, которое принимает в качестве входных данных тип рисунка, который будет сделан в сцене 1 (линия, безье, фракталы и т. д.), а в сцене 2 будет генерировать указанный рисунок. Контроллер представления Scene2 имеет 2 кнопки сверху — одну для обновления — на странице будет создан новый рисунок того же типа — или кнопку «Назад» — для возврата к сцене 1 и ввода новых параметров. Он также имеет UIView, который содержит фактические рисунки внизу - код был включен в исходную публикацию. Зная все это, вы все равно порекомендуете мне использовать View, а не UIView? - person marina; 11.03.2013
comment
@marina Мой класс View на самом деле является подклассом UIView, поэтому я не понимаю вашего мнения, а не вопроса UIView. Моя главная мысль заключалась в том, что вам не нужно делать собственный код drawRect при использовании CAShapeLayer Core Animation. Вы можете либо анимировать, используя drawRect с таймерами или CADisplayLink в подклассе UIView (что немного сложно), либо используя Core Animation. Когда я использую CAShapeLayer, я делаю это в подклассе UIViewController (чтобы не путать его с альтернативой drawRect), но это личное предпочтение, так как это работает как в подклассах UIView, так и в подклассах UIViewController. - person Rob; 11.03.2013
comment
Короче говоря, нет абсолютно ничего плохого в выполнении кода CAShapeLayer в подклассе UIView (как показывает мой пример). Моя главная мысль заключается в том, что вам не нужен пользовательский код drawRect при использовании CAShapeLayer. - person Rob; 11.03.2013
comment
кажется, я начинаю понимать... Как вы рекомендуете очищать слой нижележащего рисунка, когда мне нужно создать новый? Они просто продолжают рисовать одно поверх другого... - person marina; 11.03.2013
comment
@marina Если вы сохраните ссылку на старый CAShapeLayer, вы можете просто установить его path в качестве нового пути (и повторно инициировать анимацию, если хотите). Или вы можете взять старый CAShapeLayer и вызвать removeFromSuperlayer перед добавлением нового CAShapeLayer. Оба метода работают нормально, хотя первый немного эффективнее. - person Rob; 11.03.2013
comment
@Rob Привет, не могли бы вы взглянуть на этот вопрос? Я ломал голову над этим последние пару недель, извините, если это неуместно, и просто игнорируйте это в этом случае :). stackoverflow.com/questions/42272062 / - person Lukas; 17.02.2017
comment
Удаление еще одного для @Rob stackoverflow.com/questions/43931396/ :) - person zengr; 13.05.2017