Вырезать прозрачное отверстие в UIView

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

В надежде сделать что-то вроде этого:

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

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


person tiltem    schedule 14.03.2012    source источник
comment
да, он мертв @Fattie   -  person tryKuldeepTanwar    schedule 30.08.2018


Ответы (17)


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

Файл заголовка (.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

Теперь, чтобы добавить представление с частичной прозрачностью, вам нужно импортировать пользовательский подкласс UIView PartialTransparentView, а затем использовать его следующим образом:

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

Это создаст представление с двумя прозрачными прямоугольниками. Конечно, вы можете добавить столько прямоугольников, сколько пожелаете, или просто использовать один. Приведенный выше код обрабатывает только прямоугольники, поэтому, если вы хотите использовать круги, вам придется изменить его.

person Lefteris    schedule 04.04.2013
comment
Для круглого прозрачного слоя измените прямоугольник рисования следующим образом: - person Mosib Asad; 26.07.2014
comment
Привет, @MosibAsad, не могли бы вы сообщить нам, как изменить прямоугольник рисования? Он не появился .. спасибо! - person daspianist; 25.11.2014
comment
Привет, даспианист! Смотрите мой полный ответ в следующем комментарии. Извините за задержку с ответом!! - person Mosib Asad; 23.02.2015
comment
@Lefteris потрясающая работа! - person Joan Cardona; 08.07.2015
comment
@Fry У меня была такая же проблема, и я решил ее, установив для свойства PartialTransparentView isOpaque значение NO (это также можно установить в .xib в Attribute Inspector › View › Drawing › Opaque) - person ghashi; 16.01.2017
comment
Как сделать интерактивным означает, что взаимодействие с пользователем включает только этот прозрачный слой @Lefteris - person Yogendra Patel; 01.01.2018
comment
Это также рисует черный круг для меня. Я не могу понять это. - person Rafthecalf; 18.06.2018
comment
Мне то же самое. Он рисует черный прямоугольник. isOpaque не помогает. Кому-то удалось решить? - person NikeAlive; 21.01.2019
comment
Каким-то образом установка backgroundColor делает пробел черным. Работает без установки backgroundColor - person NikeAlive; 21.01.2019

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

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)

введите описание изображения здесь

person sansa    schedule 29.03.2017

Lefteris Answer абсолютно прав, однако он создает прозрачные прямоугольники. Для 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);
        }
    }
}
person Mosib Asad    schedule 26.07.2014
comment
@mosib это не рисует более одного круга - person Bushra Shahid; 06.03.2015
comment
@ xs2bush вы найдете любое решение для рисования нескольких кругов - person Jigar; 08.03.2017
comment
@Jigar смотрите мой ответ ниже - person Bushra Shahid; 12.03.2017

Я использовал 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];
}
person mikeho    schedule 11.06.2015
comment
Это действительно отличный ответ, так как UIBezierPath обеспечивает большую гибкость! Только одно замечание: чтобы это работало, пометьте представление как непрозрачное (либо в IB, либо через код) - person mlepicki; 18.08.2015

Ответ @mosib очень помог мне, пока я не захотел нарисовать более одного круглого выреза, на мой взгляд. После небольшой борьбы я обновил свой drawRect следующим образом (код в быстром... извините за плохое редактирование):

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

}
person Bushra Shahid    schedule 06.03.2015
comment
Это прекрасно работает... если только два отверстия не перекрывают друг друга. В этом случае пересечение между ними не является дырой. Вы знаете, как это исправить? - person manueGE; 09.04.2019

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

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);
}
person MaheshShanbhag    schedule 04.04.2013

Реализация ответа @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)
        }
    }
}
person Joel Márquez    schedule 01.05.2018
comment
Это единственное, что работает для меня, но как сделать его кругом, а не квадратом? Спасибо. - person Casey Perkins; 30.04.2021

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

    }
}
person wils    schedule 31.08.2015

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

Учебник по использованию TAOverlayView

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

person Nick Yap    schedule 15.03.2016
comment
На самом деле это довольно приятное решение, которое отлично работает для меня. - person flohei; 30.03.2016
comment
Но он не поддерживает iOS 7 :( - person Alexander Perechnev; 21.07.2016

Вы можете добиться этого, задав слою вида границу.

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 равным половине ширины представления, и это даст вам пустой круг.

person Blago    schedule 11.03.2019

Что ж, мне придется ответить, так как я пропустил комментарий и заполнил форму ответа :) Я действительно хотел бы, чтобы Карстен предоставил больше информации о том, как лучше всего сделать то, что он предлагает.

Вы могли бы использовать

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

создать фоновое «цветное» изображение любой сложности. Изображение может быть создано либо программно, если вы знакомы с классами рисования, либо статически, если рамки окна предопределены.

person A-Live    schedule 14.03.2012

Закончилось "подделкой"

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);
person tiltem    schedule 15.03.2012

в этом коде создайте больше, чем круг

- (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;
}
person Jigar    schedule 09.03.2017

Включая ответ для Xamarin Studio iOS с использованием C#. Это рисует один прямоугольник со скругленными углами с 60% Альфа. В основном взято из ответа @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();
}
person Gandalf458    schedule 21.04.2017

Я использовал ответ от 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
  • Пользователь tht CALayer в качестве маски просмотра.
person manueGE    schedule 10.04.2019

Сделай наоборот! Поместите те виды, которые вы хотели бы видеть через «дыру», в отдельный вид нужного размера. Затем установите для clipsToBounds значение YES и поместите этот вид сверху. Тогда вид с «прозрачной» рамкой является самым нижним. «clipsToBounds» означает, что все, что находится за пределами рамки/отверстия, обрезается.

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

person Carsten    schedule 14.03.2012
comment
Это слишком сложно, чтобы сделать это правильно, окно может двигаться с прокруткой или любой другой анимацией, а вид сзади не должен двигаться, не так ли? Но ваше решение - хорошая точка зрения, спасибо, что поделились. - person A-Live; 15.03.2012
comment
Это не так сложно. Вам нужно правильно настроить иерархию представлений :) Что именно должно двигаться, как и что должно быть исправлено? - person Carsten; 15.03.2012
comment
Я не знаю, это не мой вопрос, знаете ли :), но возможна любая анимация, и я вряд ли ошибусь, если скажу, что более половины просмотров можно прокручивать в мире iOs. Это дает нам движение «окна», и я ожидаю, что «перспектива» не будет двигаться вместе. - person A-Live; 15.03.2012
comment
Если вы не хотите, чтобы окно/отверстие перемещалось, у вас есть два варианта: а) Добавить вид в супервид перемещаемого вида. b) Слушайте делегированные обратные вызовы перемещаемого вида и перемещайте окно/отверстие в другом направлении. В любом случае вы останетесь гибкими — вам просто нужно изменить иерархию представлений. - person Carsten; 15.03.2012
comment
а) добавление представления «перспектива» в суперпредставление «окно» сделает «проспект» невидимым, поскольку представление «окно» имеет сплошной фон б) я очень уверен, что просто нет эффективных инструментов для захвата всех событий анимации, пожалуйста, поделитесь, если вы знаете о некоторых, я очень хочу услышать. - person A-Live; 15.03.2012