はじめに
UIViewのサブクラスに透過性を与えたり透過性のある背景色を設定した際に、影の描画の挙動が変わります。
透過性の有るUIView系のコンテンツに影の描画をさせたい場合に、この記事が参考になればなと思います。
今回は以下2点について述べていきたいと思います
- UIViewの透過性と影の描画について
- 透過性のあるUIViewのサブクラスに影を設定する方法
コードはSwift,Objective-Cどちらも記述してあるので、参考にしてください
透過性と影の描画の関係について
UIView系のコンテンツに透過性もしくは透過性のある色を与えた場合に、影の描画に違いが見られます。
まず、UIButtonボタンを例に見ていきましょう
let shadowButton = UIButton(frame: CGRectMake(0, 0, 300, 50))
shadowButton.setTitle("Shadow Test", forState: .Normal)
shadowButton.setTitleColor(UIColor.blackColor(), forState: .Normal)
shadowButton.backgroundColor = UIColor.greenColor()
shadowButton.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
shadowButton.layer.shadowOpacity = 1.0
self.view.addSubview(shadowButton)
UIButton *shadowButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 300, 50)];
[shadowButton setTitle:@"Shadow Test" forState:UIControlStateNormal];
[shadowButton setTintColor:[UIColor blackColor]];
shadowButton.backgroundColor = [UIColor greenColor];
shadowButton.layer.shadowOffset = CGSizeMake(2.0f, 2.0f);
shadowButton.layer.shadowOpacity = 1.0f;
[self.view addSubview:shadowButton];
影を設定されたUIButtonのbackgroundColorのalphaを変更していくと、このような挙動になります
透過度が高くなるに伴いUIButton自体の影が消え、テキストに影が描画されていくのがわかるかと思います。
実際にはこのような挙動が自然ですよね。
動きや見え方が自然になるようにViewの透過度に合わせて影の描画が違和感なくされるようになっているんですね。
なお、UIButtonのalphaを変更しても透過度が高くなるに伴い、影の描画は消えていきます。
透過性の有るコンテンツに影を描画する方法
viewの透過度に合わせて影が描画されるので、透過性のあるUIView系のコンテンツに影設定したい場合に、うまく影の描画ができないといった問題が発生すると思います。今回は透過性のあるUIView系のコンテンツに影を描画する方法を紹介したいと思います
1.UIBezierPathで同じ大きさの影を設定する
shadowButton.backgroundColor = UIColor(colorLiteralRed: 0.0, green: 1.0, blue: 0.0, alpha: 0.5)
let shadowPath = UIBezierPath(rect: shadowButton.bounds)
shadowButton.layer.shadowOffset = CGSizeMake(2.0, 2.0)
shadowButton.layer.shadowPath = shadowPath.CGPath
shadowButton.layer.shouldRasterize = true
shadowButton.backgroundColor = [UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:0.5f];
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:shadowButton.bounds];
shadowButton.layer.shadowOffset = CGSizeMake(2.0f, 2.0f);
shadowButton.layer.shadowPath = shadowPath.CGPath;
shadowButton.layer.shouldRasterize = YES;
UIBezierPathでButtonと同じ大きさのpathをUIButtonのshadowPathに設定しています。
同じ透過度の場合と比較すると、しっかり影の描画がされているのがわかりますね。しかし、UIButton自体の背景に透過度が設定されているので、影が透けて見えて少し不自然な見え方になっています。透過度が低めの場合にこの方法がおすすめですね。
( 参照:uiview drop shadow transparency issue )
2.UIBezierPathでpathを自作して影を設定する
1の方法では、透過度が高い場合に、見え方が不自然になるといった問題点がありました。
この方法はpathを自作して影を描画する方法を紹介します
2.1 Frameの周りのみ影を設定する
UIBezierPathを用いて、今度は自作でpathを設定していきます。
今回は例としてpathはframeの周りを以下の画像のように描いていきます
shadowButton.layer.shadowOffset = CGSizeMake(0.0, 0.0)
shadowButton.backgroundColor = UIColor.clearColor()
let w: CGFloat = 6.0 //Shadow Width
let wh: CGFloat = w / 2
let sw = shadowButton.frame.width
let sh = shadowButton.frame.height
let pathRef: CGMutablePathRef = CGPathCreateMutable()
//Start Point 1
CGPathMoveToPoint(pathRef , nil, -wh, -wh)
//Top Outside 2
CGPathAddLineToPoint(pathRef, nil, sw + wh, -wh)
// 3
CGPathAddLineToPoint(pathRef, nil, sw + wh, wh)
//Top Inside 4
CGPathAddLineToPoint(pathRef, nil, wh, wh)
//Left Inside 5
CGPathAddLineToPoint(pathRef, nil, wh, sh - wh)
//Bottom Inside 6
CGPathAddLineToPoint(pathRef, nil, sw - wh, sh - wh)
//Right Inside 7
CGPathAddLineToPoint(pathRef, nil, sw - wh, wh)
// 8
CGPathAddLineToPoint(pathRef, nil, sw + wh, wh)
//Right Outside 9
CGPathAddLineToPoint(pathRef, nil, sw + wh, sh + wh)
//Bottom Outside 10
CGPathAddLineToPoint(pathRef, nil, -wh, sh + wh)
//Left Outside 11
CGPathAddLineToPoint(pathRef, nil, -wh, -wh)
CGPathCloseSubpath(pathRef)
shadowButton.layer.shadowPath = pathRef
shadowButton.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
shadowButton.backgroundColor = [UIColor clearColor];
CGFloat w = 6.0f;
CGFloat wh = w / 2;
CGFloat sw = shadowButton.frame.size.width;
CGFloat sh = shadowButton.frame.size.height;
CGMutablePathRef pathRef = CGPathCreateMutable();
//Start Point 1
CGPathMoveToPoint(pathRef, nil, -wh, -wh);
//Top Outside 2
CGPathAddLineToPoint(pathRef, nil, sw + wh, -wh);
// 3
CGPathAddLineToPoint(pathRef, nil, sw + wh, wh);
//Top Inside 4
CGPathAddLineToPoint(pathRef, nil, wh, wh);
//Left Inside 5
CGPathAddLineToPoint(pathRef, nil, wh, sh - wh);
//Bottom Inside 6
CGPathAddLineToPoint(pathRef, nil, sw - wh, sh - wh);
//Right Inside 7
CGPathAddLineToPoint(pathRef, nil, sw - wh, wh);
// 8
CGPathAddLineToPoint(pathRef, nil, sw + wh, wh);
//Right Outside 9
CGPathAddLineToPoint(pathRef, nil, sw + wh, sh + wh);
//Bottom Outside 10
CGPathAddLineToPoint(pathRef, nil, -wh, sh + wh);
//Left Outside 11
CGPathAddLineToPoint(pathRef, nil, -wh, -wh);
CGPathCloseSubpath(pathRef);
shadowButton.layer.shadowPath = pathRef;
これでframe周りのみ影が設定されていますね。
この方法であれば部分的に影を描画することもできますね。UIButtonの背景色の透過度が高くても自然に影の描画ができます
2.2 CornerRadiusを考慮して影を設定する
次はUIButtonのCornerRadiusを考慮して影を設定してみましょう。CGPathAddArcToPointを用いてpathを設定していきます
shadowButton.layer.cornerRadius = 15
shadowButton.layer.shadowOffset = CGSizeMake(0.0, 0.0)
shadowButton.backgroundColor = UIColor.clearColor()
let w: CGFloat = 6.0
let wh: CGFloat = w / 2
let sw = shadowButton.frame.width
let sh = shadowButton.frame.height
let c = shadowButton.layer.cornerRadius
let pathRef: CGMutablePathRef = CGPathCreateMutable()
//Start Point
CGPathMoveToPoint(pathRef , nil, -wh, -wh + c)
CGPathAddArcToPoint(pathRef , nil, -wh, -wh, -wh + c, -wh, c)
//Top Outside
CGPathAddLineToPoint(pathRef, nil, sw + wh - c, -wh)
CGPathAddArcToPoint(pathRef , nil, sw + wh, -wh, sw + wh, -wh+c, c)
CGPathAddLineToPoint(pathRef, nil, sw - wh, wh + c)
CGPathAddArcToPoint(pathRef , nil, sw - wh, wh, sw - wh - c, wh, c)
//Top Inside
CGPathAddLineToPoint(pathRef, nil, wh + c, wh)
CGPathAddArcToPoint(pathRef , nil, wh, wh, wh, wh + c, c)
//Left Inside
CGPathAddLineToPoint(pathRef, nil, wh, sh - wh - c)
CGPathAddArcToPoint(pathRef , nil, wh, sh - wh, wh + c, sh - wh, c)
//Bottom Inside
CGPathAddLineToPoint(pathRef, nil, sw - wh - c, sh - wh)
CGPathAddArcToPoint(pathRef , nil, sw - wh, sh - wh, sw - wh, sh - wh - c, c)
//Right Inside
CGPathAddLineToPoint(pathRef, nil, sw - wh, wh + c)
CGPathAddLineToPoint(pathRef, nil, sw + wh, -wh + c)
//Right Outside
CGPathAddLineToPoint(pathRef, nil, sw + wh, sh + wh - c)
CGPathAddArcToPoint(pathRef , nil, sw + wh, sh + wh, sw + wh - c, sh + wh, c)
//Bottom Outside
CGPathAddLineToPoint(pathRef, nil, -wh + c, sh + wh)
CGPathAddArcToPoint(pathRef , nil, -wh, sh + wh, -wh, sh + wh - c, c)
//Left Outside
CGPathAddLineToPoint(pathRef, nil, -wh, -wh + c)
CGPathCloseSubpath(pathRef)
shadowButton.layer.shadowPath = pathRef
shadowButton.layer.cornerRadius = 15;
shadowButton.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
shadowButton.backgroundColor = [UIColor clearColor];
CGFloat w = 6.0f;
CGFloat wh = w / 2;
CGFloat sw = shadowButton.frame.size.width;
CGFloat sh = shadowButton.frame.size.height;
CGFloat c = shadowButton.layer.cornerRadius;
CGMutablePathRef pathRef = CGPathCreateMutable();
//Start Point
CGPathMoveToPoint(pathRef , nil, -wh, -wh + c);
CGPathAddArcToPoint(pathRef , nil, -wh, -wh, -wh + c, -wh, c);
//Top Outside
CGPathAddLineToPoint(pathRef, nil, sw + wh - c, -wh);
CGPathAddArcToPoint(pathRef , nil, sw+wh, -wh, sw + wh, -wh + c, c);
CGPathAddLineToPoint(pathRef, nil, sw - wh, wh + c);
CGPathAddArcToPoint(pathRef , nil, sw - wh, wh, sw - wh - c, wh, c);
//Top Inside
CGPathAddLineToPoint(pathRef, nil, wh + c, wh);
CGPathAddArcToPoint(pathRef , nil, wh, wh, wh, wh + c, c);
//Left Inside
CGPathAddLineToPoint(pathRef, nil, wh, sh - wh - c);
CGPathAddArcToPoint(pathRef , nil, wh, sh - wh, wh + c, sh - wh, c);
//Bottom Inside
CGPathAddLineToPoint(pathRef, nil, sw - wh - c, sh - wh);
CGPathAddArcToPoint(pathRef , nil, sw - wh, sh - wh, sw - wh, sh - wh - c, c);
//Right Inside
CGPathAddLineToPoint(pathRef, nil, sw - wh, wh + c);
CGPathAddLineToPoint(pathRef, nil, sw + wh, -wh + c);
//Right Outside
CGPathAddLineToPoint(pathRef, nil, sw + wh, sh + wh - c);
CGPathAddArcToPoint(pathRef , nil, sw + wh, sh + wh, sw + wh - c, sh + wh, c);
//Bottom Outside
CGPathAddLineToPoint(pathRef, nil, -wh + c, sh + wh);
CGPathAddArcToPoint(pathRef , nil, -wh, sh + wh, -wh, sh + wh - c, c);
//Left Outside
CGPathAddLineToPoint(pathRef, nil, -wh, -wh + c);
CGPathCloseSubpath(pathRef);
shadowButton.layer.shadowPath = pathRef;
しっかりCornerRadiusが反映されてますね。
コードが少し複雑になりましたが、この方法ならボタンのCornerRadiusに合わせて見え方も自然になります。
これなら透過性があるUIView系サブクラスに影を付けることができますし、コンテンツを透明にすれば任意の場所に自由な影の描画も可能になります
おわりに
今回は
- UIView系サブクラスの透過性と影の関係性について
- 透過性があるコンテンツに影を設定する方法
について紹介しました。
なお、コンテンツの影の描画はmasksToBoundsがfalseになっている必要があります。(※masksToBoundsプロパティについてはUIViewのサブクラスによってmasksToBoundsの初期値が異なる参照)
今回はUIButtonの例で紹介しましたが、UILabelやUIImageViewなどのUIView系のコンテンツ全てに当てはまるので、色々活用できると思います。