2011-09-27 9 views
16

Mi chiedo come si possa animare i limiti di un CALayer in modo che, ad ogni modifica dei limiti, il livello chiami drawInContext:. Ho provato i seguenti 2 metodi sulla mia CALayer sottoclasse:Animare i limiti di un CALayer con i ridisegni

  • Impostazione needsDisplayOnBoundsChange a YES
  • Tornando YES per il + (BOOL)needsDisplayForKey:(NSString*)key per la bounds chiave

Né lavoro. CALayer sembra determinato ad usare i contenuti originali del livello e semplicemente li ridimensiona in base allo contentsGravity (che, presumo, è per le prestazioni). È una soluzione alternativa per questo o mi manca qualcosa di ovvio?

EDIT: E, per inciso, ho notato che il mio personalizzato CALayer sottoclasse non chiama initWithLayer: per creare un presentationLayer - strano.

Grazie in anticipo, Sam

+0

Un'altra cosa che non funziona: sottoclasse e sovrascrittura 'setFrame',' setBounds' e 'setPosition'. Non vengono chiamati durante l'animazione. –

+0

Non ti capisco del tutto. Cosa stai cercando di animare? Solo i limiti di CALAYER o qualcos'altro? L'animazione dei limiti è un'operazione abbastanza semplice, l'animazione dei fotogrammi è più complessa. – beryllium

+0

Immagina che il tuo livello contenga qualcosa come un pulsante con una grafica complessa ma indipendente dalle dimensioni. Se lo si anima per dire il doppio della larghezza, si animerà usando il ridimensionamento bitmap, diventando allungato e pixelato durante l'animazione, anche se si ha 'needsDisplayOnBoundsChange' YES. Solo il frame finale sarà reso correttamente con 'drawInContext:'. –

risposta

1

non sono sicuro che l'impostazione dei viewFlags sarebbe efficace. La seconda soluzione sicuramente non funzionerà:

L'implementazione predefinita restituisce NO. Le sottoclassi dovrebbero * chiamare super per le proprietà definite dalla superclasse. (Per esempio, * non provare di tornare SI oggetti attuate da CALayer, * fare la volontà hanno undefined risultati.)

È necessario impostare la modalità contenuti della vista a UIViewContentModeRedraw:

UIViewContentModeRedraw, //redraw on bounds change (calls -setNeedsDisplay) 

Check out Apple's documentation on providing content with CALayer's. Raccomandano l'uso della proprietà delegato di CALayer invece della sottoclasse, che potrebbe essere molto più semplice di quello che stai provando ora.

+0

UIViewContentModeRedraw non causa la ridistribuzione del contenuto del livello in ogni fase dell'animazione. – titaniumdecoy

+0

Davvero? Hmmm ... Sai se i metodi CALayerDelegate sono richiamati durante l'animazione? –

+0

A meno che non si utilizzi la tecnica che ho collegato nella mia risposta (che è quella di sovrascrivere il '+ + DisplayForKey di CALayer: metodo') – titaniumdecoy

6

È possibile utilizzare technique outlined here: sovrascrivere il metodo +needsDisplayForKey: di CALayer e ridisegnerà il contenuto in ogni fase dell'animazione.

+0

Questa è un'ottima risposta. Fornisci ulteriori informazioni sul link, quindi sappiamo prima di fare clic. –

+0

Questo metodo, in effetti, rende il metodo di ridisegno chiamato per ogni frame per quanto posso dire, anche se stranamente il CALayer non mostra i risultati di tali ridisegni ma continua con il contenuto allungato generato in anticipo. Eppure, è un significativo passo avanti. –

+0

@AlexanderLjungberg: questo metodo dovrebbe ridisegnare il contenuto in ogni fase dell'animazione. Ho creato un progetto di test per confermare che funzioni. Non sono sicuro di quale potrebbe essere la causa per cui il tuo layer non viene nuovamente visualizzato. – titaniumdecoy

-2

Questa è la vostra classe personalizzata:

@implementation MyLayer 

-(id)init 
{ 
    self = [super init]; 
    if (self != nil) 
     self.actions = [NSDictionary dictionaryWithObjectsAndKeys: 
         [NSNull null], @"bounds", 
         nil]; 
    return self; 
} 

-(void)drawInContext:(CGContextRef)context 
{ 
    CGContextSetRGBFillColor(context, 
          drand48(), 
          drand48(), 
          drand48(), 
          1); 
    CGContextFillRect(context, 
         CGContextGetClipBoundingBox(context)); 
} 

+(BOOL)needsDisplayForKey:(NSString*)key 
{ 
    if ([key isEqualToString:@"bounds"]) 
     return YES; 
    return [super needsDisplayForKey:key]; 
} 

@end 

sono queste aggiunte al modello Xcode 4.2 di default:

-(BOOL)application:(UIApplication*)application 
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions 
{ 
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 
     // Override point for customization after application launch. 
    self.window.backgroundColor = [UIColor whiteColor]; 
    [self.window makeKeyAndVisible]; 

     // create and add layer 
    MyLayer *layer = [MyLayer layer]; 
    [self.window.layer addSublayer:layer]; 
    [self performSelector:@selector(changeBounds:) 
       withObject:layer]; 

    return YES; 
} 

-(void)changeBounds:(MyLayer*)layer 
{ 
     // change bounds 
    layer.bounds = CGRectMake(0, 0, 
           drand48() * CGRectGetWidth(self.window.bounds), 
           drand48() * CGRectGetHeight(self.window.bounds)); 

     // call "when idle" 
    [self performSelector:@selector(changeBounds:) 
       withObject:layer 
       afterDelay:0]; 
} 

----------------- modificato:

Ok ... questo non è quello che hai chiesto :) Sorry: |

----------------- modificato (2):

E perché si avrebbe bisogno una cosa del genere? (void)display può essere utilizzato, ma la documentazione dice che è lì per l'impostazione self.contents ...

+0

Questo non funziona: + needsDisplayForKey non impone un ridisegno per "limiti" –

0

Non so se questo si qualifica interamente come una soluzione alla tua domanda, ma è stato in grado di farlo funzionare.

Ho prima pre-disegnato l'immagine del mio contenuto in un CGImageRef.

Quindi ho annullato il metodo -display del mio strato INVECE-drawInContext:. In esso ho impostato contents sul CGImage pre-renderizzato e ha funzionato.

Infine, il layer deve anche modificare lo contentsGravity predefinito su qualcosa come @"left" per evitare che l'immagine del contenuto venga ridimensionata.

Il problema che si presentava era che il contesto passato a -drawInContext: era della dimensione iniziale del livello, non della dimensione finale post-animazione. (È possibile controllare questo con i metodi CGBitmapContextGetWidth e CGBitmapContextGetHeight.)

I miei metodi sono ancora chiamati solo una volta per l'intera animazione, ma l'impostazione contenuto del livello direttamente con il metodo -display consente di passare un'immagine più grande dei confini visibili . Il metodo drawInContext: non lo consente, in quanto non è possibile disegnare al di fuori dei limiti del contesto CGContext.

Per di più la differenza tra i diversi metodi di disegno strato, vedere http://www.apeth.com/iOSBook/ch16.html

0

sto correndo nello stesso problema da poco. Questo è quello che ho scoperto:

C'è un trucco si può fare, che sta animando una copia shadow dei limiti come:

var shadowBounds: CGRect { 
    get { return bounds } 
    set { bounds = newValue} 
} 

poi ignorare del CALayer + needsDisplayForKey :.

Tuttavia, questo NON può essere ciò che si vuole fare se il disegno dipende dai limiti. Come hai già notato, l'animazione del core scala semplicemente il contenuto del livello per animare i limiti. Questo è vero anche se fai il trucco di cui sopra, cioè il contenuto viene ridimensionato anche se i limiti sono cambiati durante l'animazione. Il risultato è che l'animazione dei disegni sembra incoerente.

Come risolverlo? Poiché il contenuto è ridimensionato, puoi calcolare i valori delle variabili personalizzate che determinano il tuo disegno ridimensionandole in senso inverso in modo che il tuo disegno sul contenuto finale ma ridimensionato appaia uguale a quello originale e non livellato, quindi imposta daValori a questi valori, i valori toValue ai loro vecchi valori, li animano allo stesso tempo con limiti. Se i valori finali devono essere modificati, imposta i valori toValues ​​su questi valori finali. È necessario animare almeno una variabile personalizzata in modo da provocare il ridisegno.