2009-05-03 18 views
6

私はタイル画像を表示するためのカスタムUIViewを持っています。アニメーション中のUIViewスケーリング

- (void)drawRect:(CGRect)rect 
    { 
     ... 
     CGContextRef context = UIGraphicsGetCurrentContext();  
     CGContextClipToRect(context, 
      CGRectMake(0.0, 0.0, rect.size.width, rect.size.height));  
     CGContextDrawTiledImage(context, imageRect, imageRef); 
     ... 
    } 

このビューのサイズ変更をアニメーションしようとしています。

[UIView beginAnimations:nil context:nil]; 
[UIView setAnimationDuration:0.3]; 

// change frame of view 

[UIView commitAnimations]; 

タイルサイズは一定のままにして、タイル領域が成長することが予想されます。その代わりに、領域が拡大する一方で、ビューの元のコンテンツが新しいサイズにスケーリングされます。少なくともアニメーション中。したがって、アニメーションの最初と最後にすべてが良いです。アニメーションの間、タイルはゆがんでいます。

なぜCAは規模を拡大しようとしていますか?どうしたらそれを防ぐことができますか?私は何を取りこぼしたか?

+0

ビューのcontentModeに関連しているようです。しかし、それをUIViewContentModeLeftに設定しても実際には解決されません。それはもはやスケールされませんが、すぐに新しいフレームサイズにポップオーバーします。 UIViewContentModeRedrawに設定すると、アニメーション中にdrawRectを呼び出すようには見えません。 – tcurdt

答えて

15

アニメーションフレームごとにCore Animationがコードにコールバックしなければならない場合、それは決して速くなることはなく、カスタムプロパティのアニメーションは長い要望の機能であり、MacのCAのFAQです。

UIViewContentModeRedrawを使用すると、正しい軌道に乗ることができます。また、CAから得られる最高のものです。問題はUIKitの観点からです。フレームには2つの値しかありません。遷移の最初の値と遷移の終わりの値です。これが表示されます。 Core Animationアーキテクチャーのドキュメントを見ると、CAがどのようにすべてのレイヤープロパティーのプライベートな表現を持ち、それらの値が時間とともに変化するのかがわかります。これはフレーム補間が行われている場所であり、フレーム補間が発生したときにその変更を通知することはできません。

唯一の方法は、昔ながらの方法で時間の経過とともにビューフレームを変更するためにNSTimer(またはperformSelector:withObject:afterDelay:)を使用することです。

+0

ありがとう、私はそれを行うためにNSTimerを使いました(アニメーションと一緒にdrawRectを呼び出します)。それは私のために働いています。 – Strong

+0

@ jazou2012あなたは 'drawRect'を呼び出すべきではありません。あなたは' setNeedsDisplay'を呼び出すべきです。 –

+0

@ Jean-PhilippePelletはい、そうです。 'setNeedsDisplay'は' drawRect'を呼び出します!それが私の言うことなのです。 – Strong

2

Duncanが示すように、コアアニメーションは、サイズ変更されるたびにUIViewのレイヤーのコンテンツをフレームごとに再描画しません。あなたはタイマーを使って自分でそれを行う必要があります。

オムニの人は、あなたのケースに該当する可能性のある独自のカスタムプロパティに基づいてアニメーションを行う方法の良い例を投稿しました。その例は、それがどのように動作するかについての説明と共に、hereで見つけることができます。

4

私は別の状況で同様の問題に直面していました。このスレッドを見つけて、DuncanのNSTimerでのフレーム設定の提案を見つけました。私は同じことを達成するために、このコードを実装:

CGFloat kDurationForFullScreenAnimation = 1.0; 
CGFloat kNumberOfSteps = 100.0; // (kDurationForFullScreenAnimation/kNumberOfSteps) will be the interval with which NSTimer will be triggered. 
int gCurrentStep = 0; 
-(void)animateFrameOfView:(UIView*)inView toNewFrame:(CGRect)inNewFrameRect withAnimationDuration:(NSTimeInterval)inAnimationDuration numberOfSteps:(int)inSteps 
{ 
    CGRect originalFrame = [inView frame]; 

    CGFloat differenceInXOrigin = originalFrame.origin.x - inNewFrameRect.origin.x; 
    CGFloat differenceInYOrigin = originalFrame.origin.y - inNewFrameRect.origin.y; 
    CGFloat stepValueForXAxis = differenceInXOrigin/inSteps; 
    CGFloat stepValueForYAxis = differenceInYOrigin/inSteps; 

    CGFloat differenceInWidth = originalFrame.size.width - inNewFrameRect.size.width; 
    CGFloat differenceInHeight = originalFrame.size.height - inNewFrameRect.size.height; 
    CGFloat stepValueForWidth = differenceInWidth/inSteps; 
    CGFloat stepValueForHeight = differenceInHeight/inSteps; 

    gCurrentStep = 0; 
    NSArray *info = [NSArray arrayWithObjects: inView, [NSNumber numberWithInt:inSteps], [NSNumber numberWithFloat:stepValueForXAxis], [NSNumber numberWithFloat:stepValueForYAxis], [NSNumber numberWithFloat:stepValueForWidth], [NSNumber numberWithFloat:stepValueForHeight], nil]; 
    NSTimer *aniTimer = [NSTimer timerWithTimeInterval:(kDurationForFullScreenAnimation/kNumberOfSteps) 
               target:self 
               selector:@selector(changeFrameWithAnimation:) 
               userInfo:info 
               repeats:YES]; 
    [self setAnimationTimer:aniTimer]; 
    [[NSRunLoop currentRunLoop] addTimer:aniTimer 
           forMode:NSDefaultRunLoopMode]; 
} 

-(void)changeFrameWithAnimation:(NSTimer*)inTimer 
{ 
    NSArray *userInfo = (NSArray*)[inTimer userInfo]; 
    UIView *inView = [userInfo objectAtIndex:0]; 
    int totalNumberOfSteps = [(NSNumber*)[userInfo objectAtIndex:1] intValue]; 
    if (gCurrentStep<totalNumberOfSteps) 
    { 
     CGFloat stepValueOfXAxis = [(NSNumber*)[userInfo objectAtIndex:2] floatValue]; 
     CGFloat stepValueOfYAxis = [(NSNumber*)[userInfo objectAtIndex:3] floatValue]; 
     CGFloat stepValueForWidth = [(NSNumber*)[userInfo objectAtIndex:4] floatValue]; 
     CGFloat stepValueForHeight = [(NSNumber*)[userInfo objectAtIndex:5] floatValue]; 

     CGRect currentFrame = [inView frame]; 
     CGRect newFrame; 
     newFrame.origin.x = currentFrame.origin.x - stepValueOfXAxis; 
     newFrame.origin.y = currentFrame.origin.y - stepValueOfYAxis; 
     newFrame.size.width = currentFrame.size.width - stepValueForWidth; 
     newFrame.size.height = currentFrame.size.height - stepValueForHeight; 

     [inView setFrame:newFrame]; 

     gCurrentStep++; 
    } 
    else 
    { 
     [[self animationTimer] invalidate]; 
    } 
} 

私はそれは、フレームとその動作を完了するためにタイマーを設定するには長い時間がかかることが分かりました。おそらく、このアプローチを使用して-setFrameを呼び出すビュー階層がどの程度深いかによって異なります。そしておそらくこれが、ビューが最初にサイズ変更され、次にsdkの起点にアニメートされる理由です。または、おそらく、パフォーマンスが妨げられたために、自分のコードのタイマーメカニズムに問題がありますか?

非常に遅いですが、おそらく私のビュー階層が深すぎるためです。

+2

あなたのコードは私のビュー(バックグラウンドビューとテキストオーバーレイ)に適しています。ただし、changeFrameWithAnimationの最上部で[self.animationTimer invalidate]を2回、アニメーションの継続時間を0.3に変更し、ステップ数を変更しました。今のように、あなたは10msの周期でタイマーを呼び出していますが、60Hzのリフレッシュレートの場合は16msごとにタイマーを実行するだけです。 – Jon

+0

ありがとう@ジョン、haventは60Hzのリフレッシュレートを考えていましたが、私の場合、プロセス全体が遅くなり、ビューの階層が重くなってしまい、予想よりも多くの時間を話していました。したがって、要件自体を変更する必要がありました! –

1

を見てすることがありますが、アニメーションの再生時間のための問題ではありません、あなたはCADisplayLinkを使用することができます。 UIBezierPathsなどのカスタムUIView描画のスケーリングのアニメーションを円滑にします。以下は、画像に適用できるサンプルコードです。

私のカスタムビューのivarsには、displayLinkとしてCADisplayLink、2つのCGRect:toFrameとfromFrame、durationとstartTimeが含まれています。

- (void)yourAnimationMethodCall 
{ 
    toFrame = <get the destination frame> 
    fromFrame = self.frame; // current frame. 

    [displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // just in case  
    displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animateFrame:)]; 
    startTime = CACurrentMediaTime(); 
    duration = 0.25; 
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 
} 

- (void)animateFrame:(CADisplayLink *)link 
{ 
    CGFloat dt = ([link timestamp] - startTime)/duration; 

    if (dt >= 1.0) { 
     self.frame = toFrame; 
     [displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 
     displayLink = nil; 
     return; 
    } 

    CGRect f = toFrame; 
    f.size.height = (toFrame.size.height - fromFrame.size.height) * dt + fromFrame.size.height; 
    self.frame = f; 
} 

パフォーマンスはテストされていませんが、スムーズに動作します。

1

境界線を持つUIViewでサイズ変更をアニメーション化しようとすると、この質問に遭遇しました。私は、同様にここに到着する他の人たちもこの答えから恩恵を受けることを願っている。もともと私は、カスタムUIViewのサブクラスを作成し、次のようにのdrawRectメソッドをオーバーライドした

- (void)drawRect:(CGRect)rect 
{ 
    CGContextRef ctx = UIGraphicsGetCurrentContext(); 

    CGContextSetLineWidth(ctx, 2.0f); 
    CGContextSetStrokeColorWithColor(ctx, [UIColor orangeColor].CGColor); 
    CGContextStrokeRect(ctx, rect); 

    [super drawRect:rect]; 
} 

これは、他の人が言及したようなアニメーションと組み合わせたときに問題のスケーリングにつながりました。ボーダーの上端と下端は、あまりにも厚くなったり、薄くなったり、縮尺が変わったりします。この効果は、スローアニメーションを切り替えると簡単に目立つようになります。ソリューションはカスタムサブクラスを放棄し、代わりにこのアプローチを使用していた

[_borderView.layer setBorderWidth:2.0f]; 
[_borderView.layer setBorderColor:[UIColor orangeColor].CGColor]; 

これはアニメーションの間のUIView上の境界線をスケーリングの問題を修正しました。

関連する問題