2016-04-19 11 views
0

Ich arbeite an einem kreisförmigen Fortschrittsbalken, den ich animieren möchte. Alles zeichnet wie erwartet und funktioniert, aber wenn ich versuche zu animieren, bekomme ich sofort eine Änderung statt der Animation. Jede Hilfe wäre willkommen.Implizite Animation auf CALayer nicht animieren

Hier ist meine Schicht:

@interface CircularProgressLayer : CALayer 

@property (nonatomic, assign) CGFloat progressDegrees; 
@property (nonatomic, assign) CGFloat trackWidth; 

@end 

@implementation CircularProgressLayer : CALayer 

@dynamic progressDegrees; 
@dynamic trackWidth; 

- (id)initWithLayer:(id)layer 
{ 
    if ((self = [super initWithLayer:layer])) 
    { 
     if ([layer isKindOfClass:[CircularProgressLayer class]]) 
     { 
      self.progressDegrees = ((CircularProgressLayer*)layer).progressDegrees; 
     } 
    } 

    return self; 
} 

// Instruct to Core Animation that a change in the custom property should automatically trigger a redraw of the layer. 
+ (BOOL)needsDisplayForKey:(NSString*)key 
{ 
    if ([key isEqualToString:@"progressDegrees"]) 
    { 
     return YES; 
    } 
    if ([key isEqualToString:@"trackWidth"]) 
    { 
     return YES; 
    } 

    return [super needsDisplayForKey:key]; 
} 

// Needed to support implicit animation of this property. 
// Return the basic animation the implicit animation will leverage. 
- (id<CAAction>)actionForKey:(NSString *)key 
{ 
    if ([key isEqualToString:@"progressDegrees"]) 
    { 
     CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key]; 
     animation.fromValue = [[self presentationLayer] valueForKey:key]; 
     animation.duration = 5.0; 
     animation.fillMode = kCAFillModeForwards; 
     animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; 

     return animation; 
    } 
    return [super actionForKey:key]; 
} 

Wie ich es verstehe, in der actionForKey: die Animation zurückkehren sollte, dass alle sein wird benötigt, um die Animation Arbeit zu machen, aber ich habe nichts bekommen. Alle meine Zeichnungen sind derzeit in der drawLayer:inContext: Methode meiner Ansicht enthalten, die diese Schicht implementiert (weil sie als drawRect-Code entstand, bevor ich lernte, dass ich sie in eine Ebene legen musste, um sie zu animieren, und ich nicht alles konvertiert hatte), aber die sample code ich heruntergeladen von Apple macht es so aussehen, das ist ganz in Ordnung. Hier

ist der Zeichencode:

-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context 
{ 
    UIGraphicsPushContext(context); 
    [self drawTrackInContext:context]; 
    [self drawProgressInContext:context]; 
    UIGraphicsPopContext(); 
} 

- (void)drawProgressInContext:(CGContextRef)context 
{  
    if (self.progress < 1) 
    { 
     // No progress, get out 
     return; 
    } 

    CGSize size = self.bounds.size; 

    FlipContextVertically(size); 
    CGContextSaveGState(context); 
    { 
     UIBezierPath *circle = [self circle]; 
     CGContextSetLineWidth(context, 0.0); 

     CGFloat degrees = 360.0; 

     for (int i = 0; i < degrees; i++) 
     { 
      if (i > ((RMONCircularProgressLayer *)self.layer).progressDegrees) 
      { 
       break; 
      } 

      CGFloat theta = 2 * M_PI * (CGFloat) i/degrees; 
      CGFloat x = self.radius * sin(theta); 
      CGFloat y = self.radius * cos(theta); 

      MovePathCenterToPoint(circle, CGPointMake(x, y)); 
      OffsetPath(circle, CGSizeMake(size.width/2, size.height/2)); 

      CGContextSetFillColorWithColor(context, [self colorForDegree:i].CGColor); 
      CGContextAddPath(context, circle.CGPath); 
      CGContextDrawPath(context, kCGPathFillStroke); 
     } 
    } 
    CGContextRestoreGState(context); 
} 

- (void)drawTrackInContext:(CGContextRef)context 
{ 
    CGContextSaveGState(context); 
    { 
     // Draw the circle covering middle of the screen 
     CGSize size = self.bounds.size; 

     CGContextSetLineWidth(context, self.trackWidth); // will only be filled color 

     CGContextSetStrokeColorWithColor(context, self.trackColor.CGColor); 
     CGContextSetFillColorWithColor(context, self.fillColor.CGColor); 
     CGContextSetAlpha(context, 1.0); 

     CGContextAddArc(context, (CGFloat)size.width/2.0, (CGFloat)size.height/2.0, self.radius, 0, (CGFloat)M_PI*2.0, 1); 
     CGContextDrawPath(context, kCGPathFillStroke); 
    } 
    CGContextRestoreGState(context); 
} 

drawTrackInContext zieht nur einen Kreis, in dem der Fortschritt Kreis wird enthalten sein. ist der Zeichnungscode, den ich erwarten zu animieren. Es gibt einige Hilfefunktionen von iOS Drawing von Erica Sadun, die einige Kontext- und Pfadmanipulationen durchführen, die ich nicht eingeschlossen habe, die Funktionsnamen sollten ziemlich selbsterklärend sein.

Jede Hilfe würde sehr geschätzt werden.

FYI: Ich bin mehr als bereit, dies zu einer expliziten Animation zu machen, wenn das einfacher ist, aber ich ging in diese Richtung, weil ich das auch nicht machen konnte.

+0

Bitte zeigen Sie Ihren Code innerhalb 'drawLayer: inContext:' –

+0

Hinzugefügt den Zeichencode, weiß nicht, warum ich es vergessen habe. –

Antwort

2

Wenn eine Ebeneneigenschaft animiert ist, ändert sich der Wert dieser Eigenschaft nicht im Laufe der Zeit, um die Zwischenwerte darzustellen. Stattdessen werden die Werte der Eigenschaft für den jeweiligen Moment in der Ebenenzeit aus den aktiven Animationen abgeleitet und in der Ebene presentationLayer der Ebene verfügbar gemacht.

Wenn eine Animation aktiv ist, wird drawInContext: auf der Präsentationsschicht und nicht auf der Hauptschicht aufgerufen. In ähnlicher Weise empfängt -drawLayer:inContext: die Präsentationsschicht und nicht die Hauptschicht.

Das Problem, das auftritt, ist, dass -drawProgressInContext: den Layer nicht von der aufrufenden Funktion verwendet und stattdessen self.layer.progressDegrees liest, um den Wert zum Zeichnen zu erhalten, der immer der zugewiesene Wert ist. Die Lösung ist die Schicht auf diese Funktion zu übergeben und den Wert zu lesen:

- (void)drawProgressInLayer:(CALayer *)layer context:(CGContextRef)context 
{ 
    ... 
    if (i > ((RMONCircularProgressLayer *)layer).progressDegrees) { 
     ... 

Sie haben auch ein Problem, wenn Sie auf if (self.progress < 1) abbrechen. Dadurch wird es nicht animiert zu animieren, wenn von einem Wert ungleich Null auf Null gegangen wird. Derselbe Fix würde gelten: if ((RMONCircularProgressLayer *)layer).progressDegrees < 1)

+0

Vielen Dank für die ausführliche Erklärung. Ich rannte über den Wert der Präsentationsebenen, um die Animation zum Laufen zu bringen, aber ich musste einen if (isAnimating) -Block hinzufügen, um zwischen den 2 zu konvertieren, und ich wusste, dass es einfacher sein musste. Ich habe nicht bemerkt, dass mein Zeichencode mit 2 verschiedenen Layern aufgerufen wurde. Genial. –