【问题标题】:Invert simple UIView mask (cut hole instead of clip to circle) [duplicate]反转简单的 UIView 蒙版(切孔而不是剪辑到圆形)[重复]
【发布时间】:2021-06-17 21:07:40
【问题描述】:

我试图避免使用CAShapeLayer,因为我需要使用CABasicAnimation,而不是UIView.animate。因此,我只是使用 UIView 的 mask 属性来屏蔽视图。这是我目前的代码:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let imageView = UIImageView(frame: CGRect(x: 50, y: 50, width: 200, height: 300))
        imageView.image = UIImage(named: "TestImage")
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        view.addSubview(imageView)
        
        let maskView = UIView(frame: CGRect(x: 100, y: 100, width: 80, height: 80))
        maskView.backgroundColor = UIColor.blue /// ensure opaque
        maskView.layer.cornerRadius = 10
        
        imageView.mask = maskView /// set the mask
    }
}
Without imageView.mask = maskView With imageView.mask = maskView

它使图像视图的一部分可见。但是,这就是我想要的:

我怎样才能在其中切一个洞,而不是让部分图像视图可见?

【问题讨论】:

  • 你想做什么样的动画?将蒙版设置为不同的形状/大小?
  • 我之前跟你说了什么?蒙版中的所有内容都取决于其像素的透明度。你的倒退了。如果你想要一个洞,那么圆角矩形需要是透明的,其余的需要是不透明的。
  • 此外,图层可以设置动画——事实上,所有的动画最终都是图层动画——所以你不使用图层蒙版的理由是没有意义的。但是,这与问题无关。 :)
  • 啊,是的。所以子视图技术在这里不会那么容易起作用。但是你看,你改变了问题的条件,不是吗?尽管如此,我所说的面具的工作原理保持不变。
  • 请注意,这类问题有很多个答案;只需搜索:stackoverflow.com/search?q=%5Bswift%5D+mask+hole

标签: ios swift uiview mask


【解决方案1】:

您可以创建一个图像视图并将其设置为您的蒙版。请注意,这不适用于动画。如果您想将遮罩设置为不同形状的动画,您应该将遮罩添加到视图的 CALayer 并使用 CALayerAnimation,正如您所提到的。没那么糟糕。

下面我将概述如何生成具有透明部分(孔)的图像,您可以将其用作图像视图中的蒙版。但是,如果您的目标是为孔的大小、形状或位置设置动画,这将不起作用。您必须为每一帧重新生成遮罩图像,这会非常慢。

以下是使用图像视图作为蒙版的静态视图的效果:

使用 UIGraphicsBeginImageContextWithOptions() UIGraphicsImageRenderer 创建一个图像,该图像对您的大部分图像都是不透明的,并且在您想要一个洞的地方有一个透明的“洞”。

然后在您的图像视图中安装该图像,并使该图像视图成为您的掩码。

创建带有透明圆角矩形“孔”的大部分不透明图像的代码可能如下所示:

/**
 Function to create a UIImage that is mostly opaque, with a transparent rounded rect "knockout" in it. Such an image might be used ask a mask
 for another view, where the transparent "knockout" appears as a hole in the view that is being masked.
    - Parameter size:  The size of the image to create
    - Parameter transparentRect: The (rounded )rectangle to make transparent in the middle of the image.
    - Parameter cornerRadius: The corner radius ot use in the transparent rectangle. Pass 0 to make the rectangle square-cornered.
 */
func imageWithTransparentRoundedRect(size: CGSize, transparentRect: CGRect, cornerRadius: CGFloat) -> UIImage? {
    let renderer = UIGraphicsImageRenderer(size: size)
    let image = renderer.image { (context) in
        let frame = CGRect(origin: .zero, size: size)
        UIColor.white.setFill()
        context.fill(frame)
        let roundedRect = UIBezierPath(roundedRect: transparentRect, cornerRadius: cornerRadius)
        context.cgContext.setFillColor(UIColor.clear.cgColor)
        context.cgContext.setBlendMode(.clear)

        roundedRect.fill()
    }
    return image
}

还有一个viewDidLoad 方法安装一个带有一个带有孔的掩码图像视图的 UIImageView 可能看起来像这样:

override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .cyan
        let size = CGSize(width: 200, height: 300)
        let origin = CGPoint(x: 50, y: 50)
        let frame =  CGRect(origin: origin, size: size)
        let imageView = UIImageView(frame: frame)
        imageView.image = UIImage(named: "TestImage")
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        view.addSubview(imageView)
        imageView.layer.borderWidth = 2

        //Create a mask image view the same size as the (image) view we will be masking
        let maskView = UIImageView(frame: imageView.bounds)

        //Build an opaque UIImage with a transparent "knockout" rounded rect inside it.
        let transparentRect = CGRect(x: 100, y: 100, width: 80, height: 80)
        let maskImage = imageWithTransparentRoundedRect(size: size, transparentRect: transparentRect, cornerRadius: 20)

        //Install the image with the "hole" into the mask image view
        maskView.image = maskImage

        //Make the maskView the ImageView's mask
        imageView.mask = maskView /// set the mask
    }
}

我使用上面的代码创建了一个示例项目。你可以在这里从 Github 下载它:

https://github.com/DuncanMC/UIImageMask.git

我刚刚更新了项目,还展示了如何使用 CAShapeLayer 作为图像视图层上的蒙版来做同样的事情。这样做,可以对遮罩层的路径进行动画更改。

新版本有一个分段控件,可让您选择是使用视图的 mask 属性中的 UIImage 来屏蔽图像视图,还是通过用作图像视图层上的蒙版的 CAShapeLayer。

对于 CAShapeLayer 版本,遮罩层的路径是整个图像视图大小的矩形,其中绘制了第二个较小的圆角矩形。然后将形状图层上的缠绕规则设置为“偶数/奇数”规则,这意味着如果您必须跨越偶数个形状边界才能到达某个点,则该点被视为在形状之外。这使您能够创建我们需要的空心形状。

当您选择图层蒙版选项时,它会启用一个动画按钮,该按钮会随机更改蒙版中的“剪切”透明矩形。

创建掩码路径的函数如下所示:

func maskPath(transparentRect: CGRect, cornerRadius: CGFloat) -> UIBezierPath {
    let fullRect = UIBezierPath(rect: maskLayer.frame)
    let roundedRect = UIBezierPath(roundedRect: transparentRect, cornerRadius: cornerRadius)
    fullRect.append(roundedRect)
    return fullRect
}

做动画的函数是这样的:

@IBAction func handleAnimateButton(_ sender: Any) {

    //Create a CABasicAnimation that will change the path of our maskLayer
    //Use the keypath "path". That tells the animation object what property we are animating
    let animation = CABasicAnimation(keyPath: "path")

    animation.autoreverses = true //Make the animation reverse back to the oringinal position once it's done

    //Use ease-in, ease-out timing, which looks smooth
    animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)

    animation.duration = 0.3 //Make each step in the animation last 0.3 seconds.


    let transparentRect: CGRect

    //Randomly either animate the transparent rect to a different shape or shift it
    if Bool.random() {
        //Make the transparent rect taller and skinnier
        transparentRect = self.transparentRect.inset(by: UIEdgeInsets(top: -20, left: 20, bottom: -20, right: 20))
    } else {
        //Shift the transparent rect to by a random amount that still says inside the image view's bounds.
        transparentRect = self.transparentRect.offsetBy(dx: CGFloat.random(in: -100...20), dy: CGFloat.random(in: -100...100))
    }

    let cornerRadius: CGFloat = CGFloat.random(in: 0...30)
    //install the new path as the animation's `toValue`. If we dont specify a `fromValue` the animation will start from the current path.
    animation.toValue = maskPath(transparentRect: transparentRect, cornerRadius: cornerRadius).cgPath

    //add the animation to the maskLayer. Since the animation's `keyPath` is "path",
    //it will animate the layer's "path" property to the "toValue"
    maskLayer.add(animation, forKey: nil)

    //Since we don't actually change the path on the mask layer, the mask will revert to it's original path once the animation completes.
}

结果(使用我自己的示例图像)如下所示:

基于 CALayer 的蒙版动画示例如下所示:

【讨论】:

  • 没有人应该再使用UIGraphicsBeginImageContextWithOptions了。
  • 好点。我溜回了老路。关闭以编辑我的答案...
  • 哇!哟,这太疯狂了!不过,这项努力.. 非常感谢!
  • 我在 Github 项目中放了一个彩蛋,上面的 GIF 中没有。 (试试项目中的图层蒙版动画。)
猜你喜欢
  • 2017-10-08
  • 2021-02-12
  • 2012-05-21
  • 2019-07-22
  • 2021-04-07
  • 2017-02-13
  • 2010-11-03
  • 2013-10-05
  • 1970-01-01
相关资源
最近更新 更多