2009-05-03 2 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의 관점에서 볼 때 프레임에는 두 가지 값만 있습니다. 즉, 전환 시작 부분의 값과 전환 마지막 부분의 값이며 이는 사용자가보고있는 것입니다. Core Animation 아키텍처 문서를 보면 CA가 모든 레이어 속성을 개인적으로 표현한 방식과 그 값이 시간에 따라 어떻게 변하는지를 알 수 있습니다. 이것이 프레임 보간이 일어나는 곳이며, 프레임 보간이 일어날 때 그 변화를 통지받을 수 없습니다.

유일한 방법은 NSTimer (또는 performSelector:withObject:afterDelay:)을 사용하여 시간 경과에 따라보기 프레임을 변경하는 것입니다. 예전 방식입니다.

+0

감사합니다. NSTimer를 사용하여 애니메이션과 함께 drawRect를 호출했습니다. 그것은 나를 위해 일하고있다. – Strong

+0

@ jazou2012'drawRect'를 호출해서는 안되며,'setNeedsDisplay'를 호출해야합니다. –

+0

@ Jean-PhilippePellet 네, 맞습니다. 'setNeedsDisplay'는'drawRect'를 호출합니다! 그리고 그것은 내가 말하는 것을 의미합니다. – Strong

2

Duncan이 지적했듯이 Core Animation은 크기가 조정될 때마다 프레임마다 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

코드가 내보기 (배경보기 및 텍스트 오버레이)에 효과적입니다. 2 개의 변경이 필요 : 1. changeFrameWithAnimation의 상단에서 [self.animationTimer invalidate], 2. 애니메이션 지속 시간을 0.3 단계로 변경. 지금 당장 타이머를 10ms 주기로 호출하지만, 60Hz 재생 빈도로 16ms마다 수행하면됩니다. – Jon

+0

감사합니다. @ 존, havent는 60Hz 재생 빈도를 생각했지만 내 경우에는 전체 프로세스가 내보기 계층이 무거워서 따라서 예상보다 많은 시간을 이야기하고 있습니다. 따라서 요구 사항 자체를 변경해야했습니다! –

1

좀보고 할 수 있습니다 애니메이션 지속 시간에 대한 문제가되지 않습니다, 당신은 CADisplayLink를 사용할 수 있습니다. 예를 들어 UIBezierPaths를 그리는 사용자 정의 UIView 도면의 애니메이션을 부드럽게 처리합니다. 아래는 이미지에 적용 할 수있는 샘플 코드입니다.

내 사용자 정의보기의 ivars는 displayLink를 CADisplayLink로, 두 개의 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에 테두리를 확장으로 문제를 해결 :

솔루션

사용자 정의 서브 클래스를 포기하고 대신이 방법을 사용하는 것이 었습니다.