Iphone UIView在动画期间缩放

Iphone UIView在动画期间缩放,iphone,cocoa-touch,uikit,core-animation,Iphone,Cocoa Touch,Uikit,Core Animation,我有一个自定义UIView来显示平铺图像 - (void)drawRect:(CGRect)rect { ... CGContextRef context = UIGraphicsGetCurrentContext(); CGContextClipToRect(context, CGRectMake(0.0, 0.0, rect.size.width, rect.size.height));

我有一个自定义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要尝试扩展?我如何防止它这样做?我错过了什么?

如果核心动画必须为每个动画帧调用您的代码,它将永远不会像现在这样快,自定义属性的动画一直是Mac上CA的一项长期要求的功能和常见问题

使用
UIViewContentModeRedraw
是正确的方法,也是您从CA获得的最佳方法。问题是从UIKit的角度来看,帧只有两个值:转换开始时的值和转换结束时的值,这就是您看到的。如果查看核心动画体系结构文档,您将看到CA如何拥有所有层特性的私有表示及其值随时间而变化。这就是帧插值发生的地方,当发生更改时,无法通知您


因此,唯一的方法是使用
NSTimer
(或
性能选择器:withObject:afterDelay:
)随时间改变视图帧,这是一种老式的方法。

正如Duncan所指出的,核心动画不会在调整UIView层的大小时每一帧都重新绘制它的内容。你需要自己用定时器来完成


Omni的同事们发布了一个很好的例子,说明了如何根据您自己的自定义属性制作动画,这可能适用于您的案例。这个例子,以及它如何工作的解释,都可以找到。

我在另一个环境中面临类似的问题,我偶然发现了这个线索,发现了邓肯关于在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];
    }
}
CGFloat kDurationForFullScreenAnimation=1.0;
CGFloat kNumberOfSteps=100.0;//(kDurationForFullScreenAnimation/kNumberOfSteps)将是触发NSTimer的时间间隔。
int gCurrentStep=0;
-(void)animateFrameOfView:(UIView*)查看音调新帧:(CGRect)在具有AnimationDuration:(NSTimeInterval)inAnimationDuration的新帧中
{
CGRect originalFrame=[inView frame];
CGFloat differenceInXOrigin=originalFrame.origin.x-innewframe-rect.origin.x;
CGFloat differenceInYOrigin=originalFrame.origin.y-innewframe-rect.origin.y;
CGFloat stepValueForXAxis=起点/脚背的差异;
CGFloat stepValueForYAxis=不同的起点/脚背;
CGFloat differenceInWidth=originalFrame.size.width-innewframe.size.width;
CGFloat differenceInHeight=原始框架.size.height-新框架rect.size.height;
CGFloat STEP VALUES FOR WITH=宽度/脚背的差值;
CGFloat STEP VALUES FORHEIGH=身高/脚背差;
gCurrentStep=0;
NSArray*info=[NSArray ARRAY WITH OBJECTS:INVIW、[NSNumber NUMBER WITH FLOAT:inSteps]、[NSNumber WITH FLOAT:STEPPALUEFORXAXIS]、[NSNumber NUMBER WITHFLOAT:STEPPALUEFORYAXIS]、[NSNumber WITHFLOAT:STEPPALUEFORHEIGHT]、nil];
NSTimer*aniTimer=[NSTimer timer with timeinterval:(kDurationForFullScreenAnimation/kNumberOfSteps)
目标:自我
选择器:@选择器(changeFrameWithAnimation:)
userInfo:info
重复:是];
[self-setAnimationTimer:aniTimer];
[[nsrunlop currentRunLoop]添加计时器:aniTimer
forMode:NSDefaultRunLoopMode];
}
-(void)changeFrameWithAnimation:(NSTimer*)初始化
{
NSArray*userInfo=(NSArray*)[inTimer userInfo];
UIView*inView=[userInfo objectAtIndex:0];
int totalNumberOfSteps=[(NSNumber*)[userInfo对象索引:1]intValue];

如果(gCurrentStep您可能需要查看

如果它不是大型动画,或者在动画持续时间内性能不是问题,则可以使用CADisplayLink。例如,它可以平滑自定义UIView图形UIBezierPath缩放的动画。下面是一个可用于图像的示例代码

我的自定义视图的IVAR包含作为CADisplayLink的displayLink和两个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;
}
-(无效)yourAnimationMethodCall
{
toFrame=
fromFrame=self.frame;//当前帧。
[displayLink removeFromRunLoop:[NSRunLoop currentRunLoop]forMode:NSRunLoopCommonModes];//以防万一
displayLink=[CADisplayLink displayLinkWithTarget:自选择器:@selector(animateName:)];
startTime=CACurrentMediaTime();
持续时间=0.25;
[displayLink addToRunLoop:[NSRunLoop currentRunLoop]forMode:NSRunLoopCommonModes];
}
-(void)animateFrame:(CADisplayLink*)链接
{
CGFloat dt=([链路时间戳]-startTime)/持续时间;
如果(dt>=1.0){
self.frame=toFrame;
[displayLink removeFromRunLoop:[NSRunLoop currentRunLoop]forMode:NSRunLoopCommonModes];
displayLink=nil;
返回;
}
CGRect f=toFrame;
f、 size.height=(toFrame.size.height-fromFrame.size.height)*dt+fromFrame.size.height;
self.frame=f;
}

请注意,我还没有测试它的性能,但它确实工作得很顺利。

当我尝试在带有边框的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];