IOS: преобразование анимации из линии в кривую Безье

Я хотел бы анимировать прямую линию, изогнутую в кривую Безье (от "_" до "n"), есть ли где-нибудь библиотека, которая может помочь мне сделать это?

Я знаю, как нарисовать кривую Безье с помощью UIBezierPath, я мог бы быстро и постепенно перерисовать преобразование, но если что-то уже происходит, это было бы здорово:-)

1 ответ

Решение

Я мог бы сделать что-то с CADisplayLink, Например, вы можете сделать это в вашем контроллере вида, используя CAShapeLayerНапример:

#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>

@interface ViewController ()

@property (nonatomic) CFTimeInterval firstTimestamp;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic) NSUInteger loopCount;

@end

static CGFloat const kSeconds = 5.0;

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self addShapeLayer];
    [self startDisplayLink];
}

- (void)addShapeLayer
{
    self.shapeLayer = [CAShapeLayer layer];
    self.shapeLayer.path = [[self pathAtInterval:0.0] CGPath];
    self.shapeLayer.fillColor = [[UIColor clearColor] CGColor];
    self.shapeLayer.lineWidth = 3.0;
    self.shapeLayer.strokeColor = [[UIColor redColor] CGColor];
    [self.view.layer addSublayer:self.shapeLayer];
}

- (void)startDisplayLink
{
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)stopDisplayLink
{
    [self.displayLink invalidate];
    self.displayLink = nil;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
    if (!self.firstTimestamp)
        self.firstTimestamp = displayLink.timestamp;

    self.loopCount++;

    NSTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);

    self.shapeLayer.path = [[self pathAtInterval:elapsed] CGPath];

    if (elapsed >= kSeconds)
    {
        [self stopDisplayLink];
        self.shapeLayer.path = [[self pathAtInterval:0] CGPath];

        self.statusLabel.text = [NSString stringWithFormat:@"loopCount = %.1f frames/sec", self.loopCount / kSeconds];
    }
}

- (UIBezierPath *)pathAtInterval:(NSTimeInterval) interval
{
    UIBezierPath *path = [UIBezierPath bezierPath];

    [path moveToPoint:CGPointMake(0, self.view.bounds.size.height / 2.0)];

    CGFloat fractionOfSecond = interval - floor(interval);

    CGFloat yOffset = self.view.bounds.size.height * sin(fractionOfSecond * M_PI * 2.0);

    [path addCurveToPoint:CGPointMake(self.view.bounds.size.width, self.view.bounds.size.height / 2.0)
            controlPoint1:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0 - yOffset)
            controlPoint2:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0 + yOffset)];

    return path;
}

@end

анимированное изображение


В качестве альтернативы, если вы хотите сделать это путем создания подклассов UIView, вы можете сделать это так:

#import "View.h"
#import <QuartzCore/QuartzCore.h>

@interface View ()

@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic) CFTimeInterval firstTimestamp;
@property (nonatomic) CFTimeInterval displayLinkTimestamp;
@property (nonatomic) NSUInteger loopCount;

@end

static CGFloat const kSeconds = 5.25;

@implementation View

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self startDisplayLink];
    }

    return self;
}

- (void)startDisplayLink
{
    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
    [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)stopDisplayLink
{
    [self.displayLink invalidate];
    self.displayLink = nil;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
    if (!self.firstTimestamp)
        self.firstTimestamp = displayLink.timestamp;

    self.displayLinkTimestamp = displayLink.timestamp;

    self.loopCount++;

    [self setNeedsDisplayInRect:self.bounds];

    NSTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);

    if (elapsed >= kSeconds)
    {
        [self stopDisplayLink];
        self.displayLinkTimestamp = self.firstTimestamp + kSeconds;
        [self setNeedsDisplayInRect:self.bounds];
        self.statusLabel.text = [NSString stringWithFormat:@"loopCount = %.1f frames/sec", self.loopCount / kSeconds];
    }
}

- (UIBezierPath *)pathAtInterval:(NSTimeInterval) interval
{
    UIBezierPath *path = [UIBezierPath bezierPath];

    [path moveToPoint:CGPointMake(0, self.bounds.size.height / 2.0)];

    CGFloat fractionOfSecond = interval - floor(interval);

    CGFloat yOffset = self.bounds.size.height * sin(fractionOfSecond * M_PI * 2.0);

    [path addCurveToPoint:CGPointMake(self.bounds.size.width, self.bounds.size.height / 2.0)
            controlPoint1:CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0 - yOffset)
            controlPoint2:CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0 + yOffset)];

    return path;
}


- (void)drawRect:(CGRect)rect
{
    NSTimeInterval elapsed = (self.displayLinkTimestamp - self.firstTimestamp);

    UIBezierPath *path = [self pathAtInterval:elapsed];

    [[UIColor redColor] setStroke];
    path.lineWidth = 3.0;
    [path stroke];
}

@end

Я тестирую оба подкласса UIView а также контроллер представления, и они оба дали примерно 60 кадров в секунду.

Другие вопросы по тегам