Dlaczego masksToBounds = YES zapobiega cieniom CALayer?

83

Za pomocą następującego fragmentu kodu dodaję efekt cienia do jednego z moich UIView. Co działa całkiem nieźle. Ale gdy tylko ustawię właściwość masksToBounds widoku na TAK . Efekt cienia nie jest już renderowany.

self.myView.layer.shadowColor = [[UIColor blackColor] CGColor];
self.myView.layer.shadowOpacity = 1.0;
self.myView.layer.shadowRadius = 10.0;
self.myView.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
self.myView.layer.cornerRadius = 5.0;
self.myView.layer.masksToBounds = YES; // <-- This is causing the Drop shadow to not be rendered
UIBezierPath *path = [UIBezierPath bezierPathWithCurvedShadowForRect:self.myView.bounds];
self.myView.layer.shadowPath = path.CGPath;
self.myView.layer.shouldRasterize = YES;

Masz jakieś pomysły na ten temat?

jchatard
źródło

Odpowiedzi:

166

Ponieważ cień jest efektem wykonywanym poza widokiem, a to masksToBounds ustawione na YES powie UIView, aby nie rysował niczego, co znajduje się poza nim.

Jeśli chcesz mieć zaokrąglony widok narożny z cieniem, proponuję zrobić to z 2 widokami:

UIView *view1 = [[UIView alloc] init];
UIView *view2 = [[UIView alloc] init];

view1.layer.cornerRadius = 5.0;
view1.layer.masksToBounds = YES;
view2.layer.cornerRadius = 5.0;
view2.layer.shadowColor = [[UIColor blackColor] CGColor];
view2.layer.shadowOpacity = 1.0;
view2.layer.shadowRadius = 10.0;
view2.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
[view2 addSubview:view1];
[view1 release];
Ekipa
źródło
Dziękuję bardzo ... Bardzo mi to pomogło. Wdzięczni… :)
innodeasapps
1
Tylko uwaga, upewnij się, że superview [view2 w tym przypadku] ma wyraźny kolor tła, to i tak był mój problem
Adam Carter
@GangstaGraham upewnij się, że dodajesz view2 jako podwidok widoku1.
pxpgraphics
Dokładnie to, czego potrzebowałem!
Rohan Sanap
17

Teraz jest iOS 6, wszystko mogło się zmienić. Odpowiedź TheSquad nie działa dla mnie, dopóki nie udało mi się dodać jeszcze jednej linii view2.layer.masksToBounds = NO;, w przeciwnym razie cień się nie pojawi. Chociaż dokumentacja mówimasksToBounds domyślnie jest NIE, mój kod pokazuje coś przeciwnego.

Oto, jak tworzę przycisk w zaokrąglonym rogu z cieniem, który jest jednym z najczęściej używanych fragmentów kodu w mojej aplikacji.

button.layer.masksToBounds = YES;
button.layer.cornerRadius = 10.0f;

view.layer.masksToBounds = NO;      // critical to add this line
view.layer.cornerRadius = 10.0f;
view.layer.shadowOpacity = 1.0f;
// set shadow path to prevent horrible performance
view.layer.shadowPath = 
    [UIBezierPath bezierPathWithRoundedRect:_button.bounds cornerRadius:10.0f].CGPath;      

[view addSubview:button];

EDYTOWAĆ

Jeśli widoki muszą być animowane lub przewijane, masksToBounds = YESwydajność podatkowa znacznie, co oznacza, że ​​animacja prawdopodobnie będzie się zacinać. Aby uzyskać zaokrąglony narożnik i cień ORAZ płynną animację lub przewijanie, użyj następującego kodu:

button.backgroundColor = [UIColor clearColor];
button.layer.backgroundColor = [UIColor redColor].CGColor;
button.layer.masksToBounds = NO;
button.layer.cornerRadius = 10.0f;

view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:_button.bounds cornerRadius:10.0f].CGPath;
view.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
view.layer.shadowRadius = 2.0f;
view.layer.masksToBounds = NO;
view.layer.cornerRadius = 10.0f;  

[view addSubview:button];
Philip007
źródło
4
+1 za pierwszą odpowiedź. Twoja zaktualizowana odpowiedź nie będzie działać w przypadku zaokrąglonych rogów. Jeśli ustawisz „masksToBounds” na „NIE”, zaokrąglone rogi znikną. Zamiast tego zmieniać, możesz użyć właściwości „shouldRasterize”, aby uzyskać dobrą wydajność.
Dinesh Raja
Działa to teraz w 2020 roku. Tak proste rozwiązanie wystarczy dodać jedną linię.
GeneCode
4

To jest wersja odpowiedzi opublikowanej przez @TheSquad w wersjach Swift 3 i IBDesignable.

Użyłem tej samej koncepcji podczas wprowadzania zmian w pliku storyboardu. Najpierw przeniosłem swój targetView (ten, który wymaga promienia narożnika i cienia) do nowego containerView . Następnie dodałem następujące wiersze kodu (Odniesienie: https://stackoverflow.com/a/35372901/419192 ), aby dodać kilka atrybutów IBDesignable dla klasy UIView:

@IBDesignable extension UIView {
/* The color of the shadow. Defaults to opaque black. Colors created
 * from patterns are currently NOT supported. Animatable. */
@IBInspectable var shadowColor: UIColor? {
    set {
        layer.shadowColor = newValue!.cgColor
    }
    get {
        if let color = layer.shadowColor {
            return UIColor(cgColor: color)
        }
        else {
            return nil
        }
    }
}

/* The opacity of the shadow. Defaults to 0. Specifying a value outside the
 * [0,1] range will give undefined results. Animatable. */
@IBInspectable var shadowOpacity: Float {
    set {
        layer.shadowOpacity = newValue
    }
    get {
        return layer.shadowOpacity
    }
}

/* The shadow offset. Defaults to (0, -3). Animatable. */
@IBInspectable var shadowOffset: CGPoint {
    set {
        layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
    }
    get {
        return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
    }
}

/* The blur radius used to create the shadow. Defaults to 3. Animatable. */
@IBInspectable var shadowRadius: CGFloat {
    set {
        layer.shadowRadius = newValue
    }
    get {
        return layer.shadowRadius
    }
}

/* The corner radius of the view. */
@IBInspectable var cornerRadius: CGFloat {
    set {
        layer.cornerRadius = newValue
    }
    get {
        return layer.cornerRadius
    }
}

Po dodaniu tego kodu wróciłem do scenorysu i po wybraniu mojego containerView mogłem teraz znaleźć nowy zestaw atrybutów w inspektorze atrybutów:

wprowadź opis obrazu tutaj

Oprócz dodawania wartości dla tych atrybutów zgodnie z moim wyborem, dodałem również promień narożnika do mojego targetView i ustawiłem właściwość masksToBounds na true.

Mam nadzieję, że to pomoże :)

Kushal Ashok
źródło
4

Wersja Swift 3.0 ze StoryBoard

Ten sam pomysł z @TheSquad. Utwórz nowy widok pod rzeczywistym widokiem i dodaj cień do dolnego widoku.

1. Utwórz widok pod rzeczywistym widokiem

Przeciągnij UIViewdo StoryBoard z tym samym ograniczeniem co widok docelowy. Zaznacz klip do powiązania dla widoku docelowego. Upewnij się również, że nowy widok jest wymieniony przed widokiem docelowym, tak aby widok docelowy obejmował nowy widok.

wprowadź opis obrazu tutaj

2. Teraz połącz nowy widok z kodem, dodaj do niego cień

To tylko próbka. Tutaj możesz robić, co chcesz

shadowView.layer.masksToBounds = false
shadowView.layer.shadowColor = UIColor.red.cgColor
shadowView.layer.shadowOpacity = 0.5
shadowView.layer.shadowOffset = CGSize(width: -1, height: 1)
shadowView.layer.shadowRadius = 3

shadowView.layer.shadowPath = UIBezierPath(rect: coverImage.bounds).cgPath
shadowView.layer.shouldRasterize = true
Fangming
źródło
2

Miałem też drastyczne problemy z wydajnością z cieniami i zaokrąglonymi rogami. Zamiast korzystać z części shadowPath, użyłem następujących linii, które doskonale rozwiązały problem wydajności:

self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = UIScreen.mainScreen.scale;
salocinx
źródło
Jednak nie można animować ani przekazywać ekranu za pomocą tego.
Van Du Tran
1

Oto jedno z rozwiązań:

     @IBOutlet private weak var blockView: UIView! {
         didSet {
          blockView.backgroundColor = UIColor.white
          blockView.layer.shadowColor = UIColor.black.cgColor
          blockView.layer.shadowOpacity = 0.5
          blockView.layer.shadowOffset = CGSize.zero

          blockView.layer.cornerRadius = 10
        }
      }
      @IBOutlet private weak var imageView: UIImageView! {
        didSet {
          imageView.layer.cornerRadius = 10
          imageView.layer.masksToBounds = true

          imageView.layer.shouldRasterize = true
        }
      }

wprowadź opis obrazu tutaj

Svitlana
źródło