iOS动画遮罩覆盖UIImage

5

我正在使用Swift 1.2,我的目标是在静态UIImage上动画显示一个图像蒙版。我实现的是我最初在Objective-C中找到的图像蒙版的Swift版本。

func maskImage(image: UIImage, mask: UIImage) -> UIImage! {

    let maskRef = mask.CGImage;

    let mask = CGImageMaskCreate(
        CGImageGetWidth(maskRef),
        CGImageGetHeight(maskRef),
        CGImageGetBitsPerComponent(maskRef),
        CGImageGetBitsPerPixel(maskRef),
        CGImageGetBytesPerRow(maskRef),
        CGImageGetDataProvider(maskRef), nil, false);

    let masked = CGImageCreateWithMask(image.CGImage, mask);
    let retImage = UIImage(CGImage: masked);
    return retImage;
}

这很好!但是,让它动起来是我的难点。

有没有一种方法可以迭代地应用具有不同水平偏移量的蒙版,或者有更好的方法来解决这个问题 - 或许可以使用CALayer实现?

感谢您的帮助!

编辑:根据已发布的答案,我添加了以下内容:

    let image = UIImage(named: "clouds");

    let imageView = UIImageView(image: image);
    let layer = CALayer();
    layer.contents = UIImage(named: "alpha-mask")?.CGImage;
    layer.bounds = CGRectMake(0, 0, image.size.width, image.size.height);

    // For other folks learning, this did not work
    //let animation = CABasicAnimation(keyPath: "bounds.origin.x");

    // This does work
    let animation = CABasicAnimation(keyPath: "position.x");

    animation.duration = 2;
    animation.delegate = self;
    animation.fillMode = kCAFillModeForwards;
    animation.repeatCount = 0;
    animation.fromValue = 0.0;
    animation.toValue = image.size.width;
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear);
    animation.removedOnCompletion = false;

    layer.addAnimation(animation, forKey: "transform");

    imageView.layer.mask = layer;

    self.addSubview(imageView);

我能够正确地看到alpha遮罩,但是动画不起作用。有什么想法吗? 编辑:我修改了上面的代码,现在它可以工作了!我需要把keyPath改成position.x。请参考上面的内容。

2
代表SO感谢您回来发布您的可运行代码,并添加“这种方法不起作用”的评论。它有助于使网站更有用。一个建议:不要替换您的代码,而是在底部添加一个“编辑”以发布您的新代码,但保留旧代码。这样可以为未来读者保留问题、答案和评论的连贯性。 - Duncan C
@DuncanC 很高兴能帮助到你。毕竟整个目的就是帮助其他人找到解决方案! - a432511
1个回答

10
你确实想要使用CALayer - 或者更确切地说,是CAShapeLayer。
你可以创建一个CAShapeLayer,并将其安装为另一图层的遮罩。
你可以创建一个CAAnimation来动画化形状图层路径的更改,或者你可以动画化图层的strokeStart和/或strokeEnd属性的更改。
如果你动画化路径,你要遵循的一个规则是确保起始路径和结束路径具有相同数量和类型的控制点。否则,动画是“未定义”的,结果可能会非常奇怪。
我有一篇开发博客文章,概述了如何完成这个过程:

http://wareto.com/using-core-animation-groups-to-create-animation-sequences-2

这主要涉及使用CAAnimationGroups,但还包括一个工作示例,演示如何对用作图像视图层掩码的CAShapeLayer进行动画更改。

下面是它创建的掩码动画的GIF - 一种“时钟擦拭”效果,可以显示和隐藏图像视图:

enter image description here

很不幸,它是用Objective-C编写的,但是在Swift中,核心动画调用几乎相同。如果您在适应过程中遇到任何问题,请告诉我。
动画代码的核心部分是这个方法:
- (IBAction)doMaskAnimation:(id)sender;
{

  waretoLogoLarge.hidden = FALSE;//Show the image view

  //Create a shape layer that we will use as a mask for the waretoLogoLarge image view
  CAShapeLayer *maskLayer = [CAShapeLayer layer];

  CGFloat maskHeight = waretoLogoLarge.layer.bounds.size.height;
  CGFloat maskWidth = waretoLogoLarge.layer.bounds.size.width;

  CGPoint centerPoint;
  centerPoint = CGPointMake( maskWidth/2, maskHeight/2);

  //Make the radius of our arc large enough to reach into the corners of the image view.
  CGFloat radius = sqrtf(maskWidth * maskWidth + maskHeight * maskHeight)/2;

  //Don't fill the path, but stroke it in black.
  maskLayer.fillColor = [[UIColor clearColor] CGColor];
  maskLayer.strokeColor = [[UIColor blackColor] CGColor];

  maskLayer.lineWidth = radius; //Make the line thick enough to completely fill the circle we're drawing

  CGMutablePathRef arcPath = CGPathCreateMutable();

  //Move to the starting point of the arc so there is no initial line connecting to the arc
  CGPathMoveToPoint(arcPath, nil, centerPoint.x, centerPoint.y-radius/2);

  //Create an arc at 1/2 our circle radius, with a line thickess of the full circle radius
  CGPathAddArc(arcPath,
               nil,
               centerPoint.x,
               centerPoint.y,
               radius/2,
               3*M_PI/2,
               -M_PI/2,
               YES);

  maskLayer.path = arcPath;

  //Start with an empty mask path (draw 0% of the arc)
  maskLayer.strokeEnd = 0.0;

  CFRelease(arcPath);

  //Install the mask layer into out image view's layer.
  waretoLogoLarge.layer.mask = maskLayer;

  //Set our mask layer's frame to the parent layer's bounds.
  waretoLogoLarge.layer.mask.frame = waretoLogoLarge.layer.bounds;

  //Create an animation that increases the stroke length to 1, then reverses it back to zero.
  CABasicAnimation *swipe = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
  swipe.duration = 2;
  swipe.delegate = self;
  [swipe setValue: theBlock forKey: kAnimationCompletionBlock];

  swipe.timingFunction = [CAMediaTimingFunction 
    functionWithName:kCAMediaTimingFunctionLinear];
  swipe.fillMode = kCAFillModeForwards;
  swipe.removedOnCompletion = NO;
  swipe.autoreverses = YES;

  swipe.toValue = [NSNumber numberWithFloat: 1.0];

  [maskLayer addAnimation: swipe forKey: @"strokeEnd"];
}

我有另一篇关于Swift的博客文章,展示了如何使用CAShapeLayer创建和动画显示饼图。该项目动画显示形状,而不是蒙版,但唯一的区别就是您将形状层安装为常规内容层还是作为另一层的蒙版,例如图像视图的后备层。您可以通过此链接查看该项目:

http://wareto.com/swift-piecharts


太好了!口罩可以从黑白图像中生成吗(不仅仅是边界)?谢谢。 - a432511
我想我找到了。layer.contents = UIImage(named: "some-image"); 看起来这个图层是根据 alpha 通道工作的,所以我需要重新创建我的遮罩图像。我会尝试一下,然后再试试你的动画魔法。谢谢! - a432511
好的。我根据您提供的内容制作了动画,但是似乎无法正常工作。请参见上文。 - a432511
太棒了!我编辑了我的问题。现在用 position.x 作为关键路径而不是 bounds.origin.x,它可以工作了。 :-) - a432511
我的原帖中有解决方案。这个答案引导我找到了解决方案。谢谢! - a432511
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接