【问题标题】:Cut transparent hole in UIView在 UIView 中切割透明孔
【发布时间】:2012-03-31 11:02: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

标签: ios objective-c uiview cgcontext


【解决方案1】:

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

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

【讨论】:

  • 对于CIRCULAR透明层,修改draw rect为:
  • 嘿@MosibAsad,你能告诉我们将draw rect修改为什么吗?它没有出现......谢谢!
  • 嘿Daspianist,在下一条评论中查看我的完整答案。抱歉回复晚了!!
  • @Lefteris 干得漂亮!
  • 这也为我画了一个黑圈。我不能说清楚。
【解决方案2】:

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

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)

【讨论】:

    【解决方案3】:

    Lefteris Answer 是绝对正确的,但是,它会创建透明的 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);
            }
        }
    }
    

    【讨论】:

    • @mosib 这不会画多个圆圈
    • @xs2bush 你找到绘制多个圆的任何解决方案
    • @Jigar 在下面看到我的回答
    【解决方案4】:

    我使用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];
    }
    

    【讨论】:

    • 这真是一个很棒的答案,因为 UIBezierPath 提供了很大的灵活性!请注意:要使其正常工作,请将视图标记为不透明(在 IB 中或通过代码)
    【解决方案5】:

    @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】:

    这将进行剪辑:

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 这是唯一对我有用的——但是如何使它成为圆形而不是方形呢?谢谢。
      • 如何使用。
      【解决方案8】:

      这个实现支持矩形和圆形,用 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);
                      }
                  }
              }
          }
      }
      

      【讨论】:

        【解决方案9】:

        这是我的一般快速实现。

        • 对于静态视图,将元组作为 (theView, isRound) 添加到 holeViews 数组中
        • 如果您想根据需要动态分配视图,请将生成器设置为某些内容,例如 {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)
                        }
                    }
                }
        
            }
        }
        

        【讨论】:

          【解决方案10】:

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

          您可以根据您的颜色和不透明度需求,通过将叠加层的backgroundColor 设置为UIColor(red: 0, green: 0, blue: 0, alpha: 0.85) 之类的内容来更改背景。

          【讨论】:

          • 这实际上是一个非常好的解决方案,非常适合我。
          • 但是不支持iOS 7 :(
          【解决方案11】:

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

          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 设置为视图宽度的一半,这将给您一个空心圆。

          【讨论】:

            【解决方案12】:

            我使用了来自 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 用作视图掩码。

            【讨论】:

              【解决方案13】:

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

              你可以使用

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

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

              【讨论】:

                【解决方案14】:

                最终“伪造”它

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

                【讨论】:

                  【解决方案15】:

                  在这段代码中创建多个圆圈

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

                  【讨论】:

                    【解决方案16】:

                    包括使用 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();
                    }
                    

                    【讨论】:

                      【解决方案17】:

                      反其道而行之!将您希望通过“洞”看到的视图放置在合适大小的单独视图中。然后将“clipsToBounds”设置为 YES 并将该视图放在顶部。带有“透明”框架的视图是最下面的。 “clipsToBounds”表示盒子/洞外的所有东西都被切断了。

                      然后您可能必须处理触摸的处理方式。但这是另一个问题。也许在相应的视图上设置 userInteractionEnabled 就足够了。

                      【讨论】:

                      • 这样做太复杂了,窗口可能会随着滚动或任何其他动画而移动,而“后面”的视图一定不能移动,不是吗?但是您的解决方案是一个很好的观点,谢谢分享。
                      • 没那么复杂。您需要正确设置视图层次结构 :) 究竟应该如何移动以及应该修复什么?
                      • 我不知道,你知道这不是我的问题 :) 但任何类型的动画都是可能的,而且我不太可能错误地说超过一半的视图在 iOs 世界中是可滚动的。这让我们的“窗口”移动了,我希望“前景”不会一起移动。
                      • 如果您不想移动“窗口”/“洞”,您有两个选择: a) 将视图添加到被移动视图的超级视图。 b) 监听正在移动的视图的委托回调,并根据另一个方向移动“窗口”/“洞”。在任何情况下,您都将保持灵活性 - 您只需重新排列视图层次结构。
                      • a) 将 'prospect' 视图添加到 'window' 超级视图将使 'prospect' 不可见,因为 'window' 视图具有实心背景 b) 我非常有信心没有有效的工具来捕捉所有动画事件,如果你知道一些,请分享,我很想听。
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-11-03
                      • 2022-07-21
                      相关资源
                      最近更新 更多