web-dev-qa-db-fra.com

Couper un trou transparent dans UIView

Vous cherchez à créer une vue comportant un cadre transparent à l'intérieur de manière à ce que les vues derrière la vue puissent être vues à travers ce cadre transparent, mais les zones extérieures à celle-ci ne seront pas visibles. Donc, essentiellement, une fenêtre dans la vue.

En espérant pouvoir faire quelque chose comme ça:

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

mais le clair ne remplace pas le noir, donc tout l’arrière-plan est noir. Des idées dans ce sens?

41
tiltem

Voici ma mise en œuvre (car j'avais besoin d'une vue avec des parties transparentes):

Fichier d'en-tête (.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

Fichier d'implémentation (.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

Maintenant, pour ajouter une vue avec une transparence partielle, vous devez importer la sous-classe UIView personnalisée de PartialTransparentView, puis l'utiliser comme suit:

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

Cela créera une vue avec 2 effets transparents . Bien sûr, vous pouvez ajouter autant de motifs que vous le souhaitez, ou tout simplement en utiliser un . devra le modifier.

43
Lefteris

Lefteris Answer a tout à fait raison, cependant, il crée des Rects transparents. Pour la couche transparente CIRCULAR, modifiez draw rect comme 

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

J'ai utilisé UIBezierPath_ pour gérer la découpe du trou transparent . Le code suivant va dans une sous-classe de la UIView pour laquelle vous voulez dessiner un trou transparent:

- (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];
}
12
mikeho

Une autre solution: Le grand rect est toute la vue (couleur jaune) et le petit est le rect transparent .

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
sansa

Cela fera la coupure:

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

La réponse de @ mosib m'a beaucoup aidé jusqu'à ce que je veuille dessiner plus d'une découpe circulaire à mon sens. Après avoir lutté un peu, j'ai mis à jour mon drawRect comme ceci (code dans Swift ... désolé, mauvaise édition):

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
Bushra Shahid

Cette implémentation prend en charge les rectangles et les cercles, écrits en 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
dichen

Si vous voulez quelque chose de rapide et efficace, j'ai ajouté une bibliothèque ( TAOverlayView ) à CocoaPods qui vous permet de créer des superpositions avec des trous rectangulaires/circulaires, permettant à l'utilisateur d'interagir avec les vues situées derrière la superposition. Je l'ai utilisé pour créer ce tutoriel pour l'une de nos applications:

Tutorial using the TAOverlayView

Vous pouvez modifier l’arrière-plan en définissant la backgroundColor de la superposition avec un paramètre tel que UIColor(red: 0, green: 0, blue: 0, alpha: 0.85), en fonction de vos besoins en matière de couleur et d’opacité.

1
Nick Yap

Voici ma mise en œuvre générale Swift.

  • Pour les vues statiques, ajoutez des nuplets au tableau holeViews en tant que (theView, isRound)
  • Si vous souhaitez attribuer les vues de manière dynamique selon mes besoins, définissez le générateur sur quelque chose, par exemple, peut-être {someViewArray.map{($0,false)}} // array of views, not round.
  • Utilisez le rayon de coin de la vue au lieu du drapeau isRound si vous le souhaitez. IsRound est tout simplement plus facile pour créer des cercles.
  • Notez que isRound est vraiment isEllipseThatWillBeRoundIfTheViewIsSquare
  • La plupart des codes n'auront pas besoin de ceux du public/internes.

J'espère que ça aide quelqu'un, grâce aux autres contributeurs

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
wils

Mise en oeuvre du @ Lefterisanswer sur 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
Joel Márquez

Y compris une réponse pour Xamarin Studio iOS en utilisant C #. Cela dessine un rectangle arrondi unique avec 60% Alpha. Surtout tiré de la réponse de @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
Gandalf458

Eh bien, je vais devoir répondre car le commentaire est manquant et remplir un formulaire de réponse:) Je voudrais vraiment que Carsten fournisse plus d'informations sur la meilleure façon de faire ce qu'il propose.

Vous pourriez utiliser

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

pour créer une image de couleur "de fond" de toute complexité. Une image peut être créée par programme si vous êtes familiarisé avec les classes de dessin ou de manière statique si les cadres de Windows sont prédéfinis.

0
A-Live

dans ce code créer plus que cercle 

- (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
Jigar

Fini par "faire semblant"

windowFrame est une propriété

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
tiltem