Вырезать прозрачное отверстие в UIView - PullRequest
43 голосов
/ 15 марта 2012

Создание представления с прозрачной рамкой внутри него, чтобы виды за представлением можно было видеть через эту прозрачную рамку, но области вне этого не будут видны сквозь.Так что, по сути, это окно в представлении.

Надеемся, что сможем сделать что-то вроде этого:

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

, но очистка не отменяет черный, поэтому весь фон черный.Любые идеи в этом направлении?

Ответы [ 17 ]

43 голосов
/ 04 апреля 2013

Это моя реализация (так как мне нужно было представление с прозрачными частями):

Файл заголовка (.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 прозрачными канатами.Конечно, вы можете добавить столько ритов, сколько пожелаете, или просто использовать один.Приведенный выше код обрабатывает только прямоугольники, поэтому, если вы хотите использовать круги, вам придется изменить его.

21 голосов
/ 26 июля 2014

Lefteris Ответ абсолютно прав, однако он создает прозрачные Rects. Для прозрачного слоя CIRCULAR измените рисование прямоугольника на

- (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 голосов
/ 11 июня 2015

Я использовал 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 голосов
/ 29 марта 2017

Другое решение: Большой прямоугольник - это весь вид (желтого цвета), а маленький - прозрачный прямоугольник.Непрозрачность цвета устанавливается.

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

6 голосов
/ 06 марта 2015

@ ответ 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

}
5 голосов
/ 04 апреля 2013

Это сделает отсечение:

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 голос
/ 01 мая 2018

Реализация @ 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 голос
/ 15 марта 2016

Если вы хотите что-то быстрое и эффективное, я добавил библиотеку ( TAOverlayView ) в CocoaPods, которая позволяет создавать наложения с прямоугольными / круглыми отверстиями, позволяя пользователю взаимодействовать с видами за наложением.Я использовал его для создания этого учебника для одного из наших приложений:

Tutorial using the TAOverlayView

Вы можете изменить фон, установив backgroundColor наложения с чем-то вроде UIColor(red: 0, green: 0, blue: 0, alpha: 0.85), в зависимости отна ваш цвет и непрозрачность потребностей.

1 голос
/ 31 августа 2015

Вот моя общая быстрая реализация.

  • Для статических представлений добавьте кортежи в массив 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 голос
/ 23 июня 2015

Эта реализация поддерживает прямоугольники и круги, написанные в 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);
                }
            }
        }
    }
}
...