在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个回答

1

这是我通用的swift实现。

  • 对于静态视图,将元组添加到holeViews数组中作为(theView,isRound)
  • 如果您想像我一样动态分配视图,请将生成器设置为某些内容,例如{someViewArray.map{($0,false)}} // array of views, not round
  • 如果您想要,可以使用视图的圆角半径而不是isRound标志,isRound只是更容易制作圆形。
  • 请注意,isRound实际上是isEllipseThatWillBeRoundIfTheViewIsSquare
  • 大多数代码不需要public/internal。

希望它能帮助某些人,感谢其他贡献者

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
你可以通过给视图的层添加边框来实现这一点。
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
最终“假装”它
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

好的,我得回答一下因为错过了评论并填写了一个答案表格 :) 我真的很想让Carsten提供更多关于他所提出的最佳方法的信息。

你可以使用

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

创建任意复杂度的背景“颜色”图像。如果您熟悉绘图类,则可以通过编程方式创建图像,或者如果窗口框架是预定义的,则可以静态地创建图像。


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

iOS UIView 添加一个洞

关键点是 evenOdd

let view = self.imageView!
let horizontalPerc = 0.8
let verticalPerc = 0.2

let bigFrame = view.frame
let bigFrameSize = bigFrame.size

let smallFrameSizeWidth = bigFrameSize.width * horizontalPerc
let smallFrameSizeHeight = bigFrameSize.height * verticalPerc

let smallFrameCenterX = view.center.x - smallFrameSizeWidth / 2
let smallFrameCenterY = view.center.y - smallFrameSizeHeight / 2

var smallRect = CGRect(
    origin: CGPoint(
        x: smallFrameCenterX,
        y: smallFrameCenterY
    ),
    size: CGSize(
        width: smallFrameSizeWidth,
        height: smallFrameSizeHeight
    )
)

let bigPath = UIBezierPath(rect: bigFrame)
let smallPath = UIBezierPath(roundedRect: smallRect, cornerRadius: 14)

bigPath.append(smallPath)
bigPath.usesEvenOddFillRule = true

let fillLayer = CAShapeLayer()
fillLayer.path = bigPath.cgPath
fillLayer.fillRule = .evenOdd
fillLayer.fillColor = UIColor.black.cgColor
fillLayer.opacity = 0.7
self.view.layer.addSublayer(fillLayer)

-3

换个方式做!将您想要通过“孔”看到的那些视图放在一个正确大小的单独视图中。然后将“clipsToBounds”设置为YES并将该视图置于顶部。具有“透明”框架的视图是最底部的。 “clipsToBounds”意味着盒子/孔外的所有内容都被剪切掉了。

然后,您可能需要处理如何处理触摸。但这是另一个问题。也许在相应的视图上设置userInteractionEnabled就足够了。


这太复杂了,窗口可能会随着滚动或任何其他动画而移动,而“后面”的视图必须保持不动,是吧?但你的解决方案是一个很好的观点,谢谢分享。 - A-Live
这并不是很复杂。你需要正确地获取视图层次结构 :) 到底应该移动什么以及什么应该固定? - Carsten
我不知道,那不是我的问题,但任何类型的动画都是可能的,而且我认为超过一半的视图在iOS世界中是可滚动的。这给了我们“窗口”移动的效果,我期望“前景”不会随之移动。 - A-Live
如果您不希望“窗口”/“孔洞”被移动,您有两个选择:a)将视图添加到正在移动的视图的超级视图中。b)监听正在移动的视图的委托回调,并根据需要将“窗口”/“孔洞”向另一个方向移动。无论哪种情况,您都将保持灵活性 - 您只需重新排列视图层次结构即可。 - Carsten
a) 将“prospect”视图添加到“window”父视图中将使“prospect”不可见,因为“window”视图具有实心背景。 b) 我非常自信地认为没有有效的工具可以捕获所有动画事件,请分享一下,如果您知道一些,我非常渴望听到。 - A-Live

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