在UIView中剪切透明孔

问题描述 投票:43回答:17

想要创建一个在其中具有透明框架的视图,以便可以通过此透明框架看到视图后面的视图,但是此外的区域将无法显示。所以在视图中基本上是一个窗口。

希望能够做到这样的事情:

 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);

但是清除不会覆盖黑色,所以整个背景都是黑色的。沿着这些方向的任何想法?

ios objective-c uiview cgcontext
17个回答
43
投票

这是我的实现(因为我确实需要透明部分的视图):

标题(.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个透明rects的视图。当然,您可以根据需要添加任意数量的rects,或者只使用一个。上面的代码只处理矩形,所以如果你想使用圆圈,你将不得不修改它。


1
投票

在Swift 4上实现@Lefteris answer

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)
        }
    }
}

0
投票

好吧,我将不得不回答错过评论并填写答案表:)我真的希望Carsten提供更多关于他提出的建议的最佳方式的信息。

你可以用

+ (UIColor *)colorWithPatternImage:(UIImage *)image

创建任何复杂性的背景“颜色”图像。如果您熟悉绘图类,则可以以编程方式创建图像,如果预定义了Windows框架,则可以静态创建图像。


0
投票

结束了“假装”它

windowFrame是一个属性

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextFillRect(context, rect);
CGRect rootFrame = [[Navigation rootController] view].frame;

CGSize deviceSize = CGSizeMake(rootFrame.size.width, rootFrame.size.height);

CGRect topRect = CGRectMake(0, 0, deviceSize.width, windowFrame.origin.y);
CGRect leftRect = CGRectMake(0, topRect.size.height, windowFrame.origin.x, windowFrame.size.height);
CGRect rightRect = CGRectMake(windowFrame.size.width+windowFrame.origin.x, topRect.size.height, deviceSize.width-windowFrame.size.width+windowFrame.origin.x, windowFrame.size.height);
CGRect bottomRect = CGRectMake(0, windowFrame.origin.y+windowFrame.size.height, deviceSize.width, deviceSize.height-windowFrame.origin.y+windowFrame.size.height);

CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillRect(context, topRect);
CGContextFillRect(context, leftRect);
CGContextFillRect(context, rightRect);
CGContextFillRect(context, bottomRect);

0
投票

在此代码中创建多个圆圈

- (void)drawRect:(CGRect)rect {

    // Drawing code
    UIColor *bgcolor=[UIColor colorWithRed:0.85 green:0.85 blue:0.85 alpha:1.0f];//Grey

    [bgcolor setFill];
    UIRectFill(rect);

    if(!self.initialLoad){//If the view has been loaded from next time we will try to clear area where required..

        // clear the background in the given rectangles
        for (NSValue *holeRectValue in _rectArray) {
            CGContextRef context = UIGraphicsGetCurrentContext();

            CGRect holeRect = [holeRectValue CGRectValue];

            [[UIColor clearColor] setFill];

            CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );

            CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
            CGContextSetBlendMode(context, kCGBlendModeClear);

            CGContextFillEllipseInRect( context, holeRectIntersection );

        }
    }

    self.initialLoad=NO;
}

0
投票

包括使用C#的Xamarin Studio iOS的答案。这将绘制一个具有60%Alpha的单个圆角矩形。主要取自@mikeho的答案

public override void Draw(CGRect rect)
{
    base.Draw(rect);

    //Allows us to draw a nice clear rounded rect cutout
    CGContext context = UIGraphics.GetCurrentContext();

    // Create a path around the entire view
    UIBezierPath clipPath = UIBezierPath.FromRect(rect);

    // Add the transparent window to a sample rectangle
    CGRect sampleRect = new CGRect(0f, 0f, rect.Width * 0.5f, rect.Height * 0.5f);
    UIBezierPath path = UIBezierPath.FromRoundedRect(sampleRect, sampleRect.Height * 0.25f);
    clipPath.AppendPath(path);

    // This sets the algorithm used to determine what gets filled and what doesn't
    clipPath.UsesEvenOddFillRule = true;

    context.SetFillColor(UIColor.Black.CGColor);
    context.SetAlpha(0.6f);

    clipPath.Fill();
}

0
投票

您可以通过为视图的图层指定边框来实现此目的。

class HollowSquareView: UIView {
  override func awakeFromNib() {
    super.awakeFromNib()

    self.backgroundColor = UIColor.clear

    self.layer.masksToBounds = true
    self.layer.borderColor = UIColor.black.cgColor
    self.layer.borderWidth = 10.0
  }
}

这将为您提供宽度为10的方形框架和透明的核心。

您还可以将图层的cornerRadius设置为视图宽度的一半,这将为您提供一个空心圆。


0
投票

我使用了Bushra Shahid的answer并且效果很好,但如果圆圈相互重叠则会有问题。

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

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视为视图蒙版。

-3
投票

反过来做吧!将您想要通过“洞”看到的视图放在正确大小的单独视图中。然后将“clipsToBounds”设置为YES并将该视图置于顶部。具有“透明”框架的视图是最重要的。 “clipsToBounds”表示盒子/孔外的所有东西都被切掉了。

那么你可能不得不处理触摸的处理方式。但这是另一个问题。也许在相应的视图上设置userInteractionEnabled就足够了。


21
投票

Lefteris答案是绝对正确的,但它会创建透明的Rects。对于CIRCULAR透明层,将draw rect修改为

- (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);
        }
    }
}

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];
}

6
投票

@ 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

}

6
投票

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

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)

enter image description here


5
投票

这将做剪辑:

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);
}

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
投票

这是我的一般快速实施。

  • 对于静态视图,将元组添加到holeViews数组中(theView,isRound)
  • 如果您想根据需要动态分配视图,请将生成器设置为某个值,例如{someViewArray.map{($0,false)}} // array of views, not round
  • 如果需要,使用视图的圆角半径而不是isRound标志,isRound更容易制作圆圈。
  • 注意,isRound确实是isEllipseThatWillBeRoundIfTheViewIsSquare
  • 大多数代码都不需要公共/内部代码。

希望它可以帮助某人,感谢其他贡献者

public class HolyView : UIView {
    public var holeViews = [(UIView,Bool)]()
    public var holeViewsGenerator:(()->[(UIView,Bool)])?

    internal var _backgroundColor : UIColor?
    public override var backgroundColor : UIColor? {
        get {return _backgroundColor}
        set {_backgroundColor = newValue}
    }

    public override func drawRect(rect: CGRect) {
        if (backgroundColor == nil) {return}

        let ctxt = UIGraphicsGetCurrentContext()

        backgroundColor?.setFill()
        UIRectFill(rect)

        UIColor.whiteColor().setFill()
        UIRectClip(rect)

        let views = (holeViewsGenerator == nil ? holeViews : holeViewsGenerator!())
        for (view,isRound) in views {
            let r = convertRect(view.bounds, fromView: view)
            if (CGRectIntersectsRect(rect, r)) {
                let radius = view.layer.cornerRadius
                if (isRound || radius > 0) {
                    CGContextSetBlendMode(ctxt, kCGBlendModeDestinationOut);
                    UIBezierPath(roundedRect: r,
                                byRoundingCorners: .AllCorners,
                                cornerRadii: (isRound ? CGSizeMake(r.size.width/2, r.size.height/2) : CGSizeMake(radius,radius))
                    ).fillWithBlendMode(kCGBlendModeDestinationOut, alpha: 1)
                }
                else {
                    UIRectFillUsingBlendMode(r, kCGBlendModeDestinationOut)
                }
            }
        }

    }
}

1
投票

如果你想要快速有效的东西,我在CocoaPods中添加了一个库(TAOverlayView),允许你创建带有矩形/圆形孔的叠加层,允许用户与叠加层后面的视图进行交互。我用它为我们的一个应用程序创建了这个教程:

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

© www.soinside.com 2019 - 2024. All rights reserved.