在UIView中剪切透明的孔

53

想要创建一个视图,该视图内部有一个透明的框架,使得可以透过这个透明的框架看到在该视图后面的视图,但是该透明框架外的区域不会显示。因此实际上就是在视图内部创建了一个窗口。

希望能够像这样做:

 CGRect hole = CGRectMake(100, 100, 250, 250);
CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillRect(context, rect);

CGContextAddRect(context, hole);
CGContextClip(context);

CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextFillRect(context, rect);

但是清除并没有覆盖黑色,因此整个背景都是黑色。有没有类似的想法?


是的,它已经死了 @Fattie - tryKuldeepTanwar
18个回答

47

这是我的实现(因为我需要一个具有透明部分的视图):

头文件(.h):

// Subclasses UIview to draw transparent rects inside the view

#import <UIKit/UIKit.h>

@interface PartialTransparentView : UIView {
    NSArray *rectsArray;
    UIColor *backgroundColor;
}

- (id)initWithFrame:(CGRect)frame backgroundColor:(UIColor*)color andTransparentRects:(NSArray*)rects;

@end

实现(.m)文件:

#import "PartialTransparentView.h"
#import <QuartzCore/QuartzCore.h>

@implementation PartialTransparentView

- (id)initWithFrame:(CGRect)frame backgroundColor:(UIColor*)color andTransparentRects:(NSArray*)rects
{
    backgroundColor = color;
    rectsArray = rects;
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.opaque = NO;
    }
    return self;
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
    [backgroundColor setFill];
    UIRectFill(rect);

    // clear the background in the given rectangles
    for (NSValue *holeRectValue in rectsArray) {
        CGRect holeRect = [holeRectValue CGRectValue];
        CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );
        [[UIColor clearColor] setFill];
        UIRectFill(holeRectIntersection);
    }

}


@end

要添加一个带有部分透明度的视图,您需要导入PartialTransparentView自定义UIView子类,然后按照以下方式使用它:

NSArray *transparentRects = [[NSArray alloc] initWithObjects:[NSValue valueWithCGRect:CGRectMake(0, 50, 100, 20)],[NSValue valueWithCGRect:CGRectMake(0, 150, 10, 20)], nil];
PartialTransparentView *transparentView = [[PartialTransparentView alloc] initWithFrame:CGRectMake(0,0,200,400) backgroundColor:[UIColor colorWithWhite:1 alpha:0.75] andTransparentRects:rects];
[self.view addSubview:backgroundView];

这将创建一个带有2个透明矩形的视图。 当然,您可以添加任意数量的矩形,或者只使用一个。 上面的代码仅处理矩形,因此如果您想要使用圆形,您将需要对其进行修改。


1
嘿@MosibAsad,你能告诉我们如何修改绘制矩形吗?它没有显示出来..谢谢! - daspianist
@Lefteris 做得太棒了! - Joan Cardona
1
这也为我画了一个黑色的圆。我无法让它清晰。 - Rafthecalf
我也遇到了同样的问题。它只绘制了一个黑色矩形,即使使用了 isOpaque 也没有帮助。有人成功解决了吗? - NikeAlive
不知何故设置backgroundColor会使间隙变黑。如果不设置backgroundColor也能正常工作。 - NikeAlive
显示剩余4条评论

35

另一种解决方案: 大矩形是整个视图(黄色),小矩形是透明的。 颜色不透明度是可设定的。

let pathBigRect = UIBezierPath(rect: bigRect)
let pathSmallRect = UIBezierPath(rect: smallRect)

pathBigRect.appendPath(pathSmallRect)
pathBigRect.usesEvenOddFillRule = true

let fillLayer = CAShapeLayer()
fillLayer.path = pathBigRect.CGPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor.yellowColor().CGColor
//fillLayer.opacity = 0.4
view.layer.addSublayer(fillLayer)

输入图像描述


21

Lefteris的答案绝对正确,但是它创建的是透明矩形。要创建圆形透明层,请修改绘制矩形为

- (void)drawRect:(CGRect)rect {

    [backgroundColor setFill];
     UIRectFill(rect);

    for (NSValue *holeRectValue in rectsArray) {
        CGRect holeRect = [holeRectValue CGRectValue];
        CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );

        CGContextRef context = UIGraphicsGetCurrentContext();

        if( CGRectIntersectsRect( holeRectIntersection, rect ) )
        {
            CGContextAddEllipseInRect(context, holeRectIntersection);
            CGContextClip(context);
            CGContextClearRect(context, holeRectIntersection);
            CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
            CGContextFillRect( context, holeRectIntersection);
        }
    }
}

3
@mosib这并没有画出多个圆。 - Bushra Shahid
@xs2bush,你找到画多个圆的解决方案了吗? - Jigar
@Jigar,请看下面我的回答。 - Bushra Shahid

12
我使用了UIBezierPath来处理透明孔的切割。 以下代码应该放入UIView的子类中,以实现在该视图上绘制透明孔:
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];

    CGContextRef context = UIGraphicsGetCurrentContext();
    // Clear any existing drawing on this view
    // Remove this if the hole never changes on redraws of the UIView
    CGContextClearRect(context, self.bounds);

    // Create a path around the entire view
    UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:self.bounds];

    // Your transparent window. This is for reference, but set this either as a property of the class or some other way
    CGRect transparentFrame;
    // Add the transparent window
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:transparentFrame cornerRadius:5.0f];
    [clipPath appendPath:path];

    // NOTE: If you want to add more holes, simply create another UIBezierPath and call [clipPath appendPath:anotherPath];

    // This sets the algorithm used to determine what gets filled and what doesn't
    clipPath.usesEvenOddFillRule = YES;
    // Add the clipping to the graphics context
    [clipPath addClip];

    // set your color
    UIColor *tintColor = [UIColor blackColor];

    // (optional) set transparency alpha
    CGContextSetAlpha(context, 0.7f);
    // tell the color to be a fill color
    [tintColor setFill];
    // fill the path
    [clipPath fill];
}

2
这是非常好的答案,因为UIBezierPath提供了很大的灵活性!只有一个注意点:要使其工作,请将视图标记为非不透明(可以在IB中或通过代码实现)。 - mlepicki

8

@mosib的回答对我很有帮助,但当我想在视图中绘制多个圆形剪切时,遇到了一些困难。经过一番努力,我更新了我的drawRect代码,如下(Swift代码...抱歉编辑不好):

override func drawRect(rect: CGRect)
{     
    backgroundColor.setFill()   
    UIRectFill(rect)

    let layer = CAShapeLayer()
    let path = CGPathCreateMutable()

    for aRect in self.rects
    {
        let holeEnclosingRect = aRect
        CGPathAddEllipseInRect(path, nil, holeEnclosingRect) // use CGPathAddRect() for rectangular hole
        /*
        // Draws only one circular hole
        let holeRectIntersection = CGRectIntersection(holeRect, rect)
        let context = UIGraphicsGetCurrentContext()

        if( CGRectIntersectsRect(holeRectIntersection, rect))
        {
        CGContextBeginPath(context);
        CGContextAddEllipseInRect(context, holeRectIntersection)
        //CGContextDrawPath(context, kCGPathFillStroke)
        CGContextClip(context)
        //CGContextClearRect(context, holeRectIntersection)
        CGContextSetFillColorWithColor(context, UIColor.clearColor().CGColor)
        CGContextFillRect(context, holeRectIntersection)
        CGContextClearRect(context, holeRectIntersection)
        }*/
    }
    CGPathAddRect(path, nil, self.bounds)
    layer.path = path
    layer.fillRule = kCAFillRuleEvenOdd
    self.layer.mask = layer

}

这个很好用...除非两个孔重叠在一起。在这种情况下,它们之间的交集不是一个孔。你知道如何解决这个问题吗? - manueGE

6
这将进行剪裁:
CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetFillColorWithColor( context, [UIColor blueColor].CGColor );
CGContextFillRect( context, rect );

CGRect holeRectIntersection = CGRectIntersection( CGRectMake(50, 50, 50, 50), rect );

if( CGRectIntersectsRect( holeRectIntersection, rect ) )
{
    CGContextAddEllipseInRect(context, holeRectIntersection);
    CGContextClip(context);
    CGContextClearRect(context, holeRectIntersection);
    CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
    CGContextFillRect( context, holeRectIntersection);
}

5

实现 @Lefteris 在 Swift 4 上的答案:

import UIKit

class PartialTransparentView: UIView {
    var rectsArray: [CGRect]?

    convenience init(rectsArray: [CGRect]) {
        self.init()

        self.rectsArray = rectsArray

        backgroundColor = UIColor.black.withAlphaComponent(0.6)
        isOpaque = false
    }

    override func draw(_ rect: CGRect) {
        backgroundColor?.setFill()
        UIRectFill(rect)

        guard let rectsArray = rectsArray else {
            return
        }

        for holeRect in rectsArray {
            let holeRectIntersection = rect.intersection(holeRect)
            UIColor.clear.setFill()
            UIRectFill(holeRectIntersection)
        }
    }
}

1
这是对我有效的唯一方式-但如何将它变成圆形而不是正方形?谢谢。 - Casey Perkins
如何使用这个。 - Bijender Singh Shekhawat

1
这个实现支持用Swift编写的矩形和圆形: PartialTransparentMaskView
class PartialTransparentMaskView: UIView{
    var transparentRects: Array<CGRect>?
    var transparentCircles: Array<CGRect>?
    weak var targetView: UIView?

    init(frame: CGRect, backgroundColor: UIColor?, transparentRects: Array<CGRect>?, transparentCircles: Array<CGRect>?, targetView: UIView?) {
        super.init(frame: frame)

        if((backgroundColor) != nil){
            self.backgroundColor = backgroundColor
        }

        self.transparentRects = transparentRects
        self.transparentCircles = transparentCircles
        self.targetView = targetView
        self.opaque = false
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func drawRect(rect: CGRect) {
        backgroundColor?.setFill()
        UIRectFill(rect)

        // clear the background in the given rectangles
        if let rects = transparentRects {
            for aRect in rects {

                var holeRectIntersection = CGRectIntersection( aRect, rect )

                UIColor.clearColor().setFill();
                UIRectFill(holeRectIntersection);
            }
        }

        if let circles = transparentCircles {
            for aRect in circles {

                var holeRectIntersection = aRect

                let context = UIGraphicsGetCurrentContext();

                if( CGRectIntersectsRect( holeRectIntersection, rect ) )
                {
                    CGContextAddEllipseInRect(context, holeRectIntersection);
                    CGContextClip(context);
                    CGContextClearRect(context, holeRectIntersection);
                    CGContextSetFillColorWithColor( context, UIColor.clearColor().CGColor)
                    CGContextFillRect( context, holeRectIntersection);
                }
            }
        }
    }
}

1

我使用了Bushra Shahid的答案,它很好用,但如果圆形重叠在一起,就会有问题。

我采用了不同的方法,在这种情况下效果很好:

class HoleView: UIView {
    var holes: [CGRect] = [] {
        didSet {
            lastProcessedSize = .zero
            createMask()
        }
    }

    private var lastProcessedSize = CGSize.zero

    override func layoutSubviews() {
        super.layoutSubviews()
        createMask()
    }

    private func createMask() {
        guard lastProcessedSize != frame.size,
            holes.count > 0
            else { return }

        let size = frame.size

        // create image
        UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
        guard let context = UIGraphicsGetCurrentContext()
            else { return }

        UIColor.white.setFill()
        context.fill(CGRect(origin: .zero, size: size))

        UIColor.black.setFill()
        holes.forEach { context.fillEllipse(in: $0) }

        // apply filter to convert black to transparent
        guard let image = UIGraphicsGetImageFromCurrentImageContext(),
            let cgImage = image.cgImage,
            let filter = CIFilter(name: "CIMaskToAlpha")
            else { return }

        filter.setDefaults()
        filter.setValue(CIImage(cgImage: cgImage), forKey: kCIInputImageKey)
        guard let result = filter.outputImage,
            let cgMaskImage = CIContext().createCGImage(result, from: result.extent)
            else { return }

        // Create mask
        let mask = CALayer()
        mask.frame = bounds
        mask.contents = cgMaskImage
        layer.mask = mask
    }
}

总结:

  • 创建一个黑白的UIImage掩码,而不是使用透明度。
  • 使用CIMaskToAlpha CIFilter将其转换为透明/白色掩码。
  • 将生成的CGImage用作CALayer的内容。
  • 将该CALayer用作视图掩码。

1
如果您想要快速而有效的解决方案,我已经将一个库(TAOverlayView)添加到CocoaPods中,它允许您创建带有矩形/圆形孔的覆盖层,使用户可以与覆盖层后面的视图进行交互。我用它来为我们的应用程序创建了这个教程:

Tutorial using the TAOverlayView

您可以通过设置覆盖层的backgroundColor来更改背景,例如UIColor(red: 0, green: 0, blue: 0, alpha: 0.85),具体取决于您的颜色和不透明度需求。

1
这实际上是一个非常棒的解决方案,对我来说非常有效。 - flohei
但它不支持iOS 7 :( - Alexander Perechnev

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