3

Here is a code which applying mask to the whole UIView:

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.view.bounds];
[maskPath appendPath:[UIBezierPath bezierPathWithArcCenter:self.mapCardsButton.center
                                                    radius:self.mapCardsButton.frame.size.height/2.0f
                                                startAngle:0.0f
                                                  endAngle:2.0f*M_PI
                                                 clockwise:NO]];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillRule  = kCAFillRuleEvenOdd;
maskLayer.fillColor = [UIColor blackColor].CGColor;
maskLayer.path = maskPath.CGPath;
self.view.layer.mask = maskLayer;

The problem is that I want to apply the mask above to the whole UIView except one specific UIButton(mapCardsButton) which is also on the same UIView. Is it possible to do?

UPD: I tried

[self.view.layer insertSublayer:maskLayer atIndex:0];

instead of

self.view.layer.mask = maskLayer;

but my self.view lost alpha channel and animation of maskLayer doesn't work anymore

Here is a project with code: https://www.dropbox.com/s/b94qcwxzoi23kwk/test_04092016.zip?dl=0

Serge
  • 2,031
  • 3
  • 33
  • 56

4 Answers4

0

The simplest way would be for the UIButton to be a sibling view of the masked view, rather than a subview. I realise that it might make logical sense for the button to be a subview (especially since it looks like your code is probably from a UIViewController subclass), so I would create a container UIView that holds all the other subviews apart from mapCardsButton and apply the mask to this new view.

So say you called the new view maskedContainerView, then:

  • self.view has two subviews: maskedContainerView and mapCardsButton
  • maskedContainerView holds all the subviews that self.view used to, except mapCardsButton
  • maskedContainerView.layer.mask = maskLayer.
stefandouganhyde
  • 4,494
  • 1
  • 16
  • 13
  • Thank you for reply. I tried your solution but it still doesn't work. Now it displays mapCardsButton but doesn't make "hole". Here is a project with code: https://www.dropbox.com/s/b94qcwxzoi23kwk/test_04092016.zip?dl=0 – Serge Apr 09 '16 at 15:01
  • 2
    Hey. Sorry about the delay. I just looked at your code. I think all that's missing is that the `maskedContainerView` should contain the content that `self.view` used to, i.e. `maskedContainerView` should have its background colour set to the grey/blue background colour that `self.view` is currently set to. Then `self.view` should be completely transparent; `self.view.backgroundColor = [UIColor clearColor]`. – stefandouganhyde Apr 19 '16 at 21:46
  • stefandouganhyde, nice workaround! I was thinking about that case – Serge Apr 20 '16 at 16:26
  • Great! So it looks and functions as you were intending, @Sergio? – stefandouganhyde Apr 20 '16 at 17:09
0

Why don't you try the following? instead of the following stack:

• mask(view) -> button

make the view background color clear, at an additional subview and do the following:

• view -> mask(subView), button

This way you will have the same visual effect, but use the bottom view as a container. In code:

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.view.bounds];
[maskPath appendPath:[UIBezierPath bezierPathWithArcCenter:self.mapCardsButton.center
                                                    radius:self.mapCardsButton.frame.size.height/2.0f
                                                startAngle:0.0f
                                                  endAngle:2.0f*M_PI
                                                 clockwise:NO]];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillRule  = kCAFillRuleEvenOdd;
maskLayer.fillColor = [UIColor blackColor].CGColor;
maskLayer.path = maskPath.CGPath;

UIView *maskedView = [[UIView alloc] initWithFrame:self.view.bounds];    
maskedView.mask = maskLayer;

self.view.backgroundColor = [UIColor clearColor];
[self.view addSubView:maskedView];
[self.view addSubView:self.mapCardsButton];
Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
trdavidson
  • 1,051
  • 12
  • 25
0

[maskedContainerView.layer insertSublayer:maskLayer atIndex:(unsigned)maskedContainerView.layer.sublayers.count];

The importance is view.layer.sublayers.count not 0 at 'atIndex'

success Please take it!

suhayl
  • 11
  • 3
0

Base on your code . I’m not sure is this what you want. enter image description here

just add the button's UIBezierPath to your maskPath. the code like this (Base on your code)

const NSInteger kHoleSize = 100;

UIButton *mapCardsButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, kHoleSize, kHoleSize )];
[mapCardsButton setTitle:@"BUTTON" forState:UIControlStateNormal];
mapCardsButton.backgroundColor = [UIColor redColor];

[self.view addSubview:mapCardsButton];



UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.view.bounds];


UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:self.view.center
                                                          radius:kHoleSize/2.0f
                                                      startAngle:0.0f
                                                        endAngle:2.0f*M_PI
                                                       clockwise:NO];

UIBezierPath *buttonPath = [UIBezierPath bezierPathWithRect:mapCardsButton.frame];
[circlePath appendPath:buttonPath];


[maskPath appendPath:circlePath];
[maskPath setUsesEvenOddFillRule:YES];

CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = maskPath.CGPath;
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.fillColor = [UIColor blackColor].CGColor;
maskLayer.opacity = 0.9;

[self.view.layer addSublayer:maskLayer];
haiLong
  • 1,116
  • 10
  • 11