透過性のあるUIViewサブクラスの影の描画と実装方法について

# はじめに
UIViewのサブクラスに透過性を与えたり透過性のある背景色を設定した際に、影の描画の挙動が変わります。
透過性の有るUIView系のコンテンツに影の描画をさせたい場合に、この記事が参考になればなと思います。

今回は以下2点について述べていきたいと思います

- UIViewの透過性と影の描画について
- 透過性のあるUIViewのサブクラスに影を設定する方法

コードはSwift,Objective-Cどちらも記述してあるので、参考にしてください

# 透過性と影の描画の関係について
UIView系のコンテンツに透過性もしくは透過性のある色を与えた場合に、影の描画に違いが見られます。
まず、UIButtonボタンを例に見ていきましょう

```swift:Swift
        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)
```
```objc:Objective-C
    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を変更していくと、このような挙動になります

![shadowgif.gif](https://qiita-image-store.s3.amazonaws.com/0/62451/e614e40e-dfac-5dca-4469-b7b50b907fd6.gif)


透過度が高くなるに伴い**UIButton自体の影が消え、テキストに影が描画されていく**のがわかるかと思います。

実際にはこのような挙動が自然ですよね。
動きや見え方が自然になるようにViewの透過度に合わせて影の描画が違和感なくされるようになっているんですね。
なお、UIButtonのalphaを変更しても透過度が高くなるに伴い、影の描画は消えていきます。

# 透過性の有るコンテンツに影を描画する方法
viewの透過度に合わせて影が描画されるので、透過性のあるUIView系のコンテンツに影設定したい場合に、うまく影の描画ができないといった問題が発生すると思います。今回は透過性のあるUIView系のコンテンツに影を描画する方法を紹介したいと思います

# 1.UIBezierPathで同じ大きさの影を設定する

```swift:Swift
    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
```
```objc:Objective-C
    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;
```
<img width="373" alt="スクリーンショット 2015-12-27 12.44.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/62451/b7ff5d3f-927b-46d0-6d2b-9ae0be775b0e.png">

UIBezierPathでButtonと同じ大きさのpathをUIButtonのshadowPathに設定しています。

<img width="374" alt="スクリーンショット 2015-12-27 12.31.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/62451/8dfe5ea0-9d79-8b90-4016-1f99fb085105.png">
同じ透過度の場合と比較すると、しっかり影の描画がされているのがわかりますね。

しかし、UIButton自体の背景に透過度が設定されているので、影が透けて見えて少し不自然な見え方になっています。透過度が低めの場合にこの方法がおすすめですね。

( 参照:[uiview drop shadow transparency issue](http://stackoverflow.com/questions/23863280/uiview-drop-shadow-transparency-issue) )

# 2.UIBezierPathでpathを自作して影を設定する
1の方法では、透過度が高い場合に、見え方が不自然になるといった問題点がありました。
この方法はpathを自作して影を描画する方法を紹介します

## 2.1 Frameの周りのみ影を設定する
UIBezierPathを用いて、今度は自作でpathを設定していきます。
今回は例としてpathはframeの周りを以下の画像のように描いていきます

<img width="695" alt="スクリーンショット 2015-12-27 13.30.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/62451/3b938b3f-5352-f722-7cf0-4d8396dc466a.png">


```swift:Swift
        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
```
```objc:Objective-C
    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;
```

<img width="328" alt="スクリーンショット 2015-12-27 13.32.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/62451/fdfac8cf-cfc7-c51a-d4c7-275963aa8a2c.png">

これでframe周りのみ影が設定されていますね。
この方法であれば部分的に影を描画することもできますね。UIButtonの背景色の透過度が高くても自然に影の描画ができます

## 2.2 CornerRadiusを考慮して影を設定する
次はUIButtonのCornerRadiusを考慮して影を設定してみましょう。CGPathAddArcToPointを用いてpathを設定していきます

```swift:Swift
        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
```
```objc:Objective-C
    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;
```
<img width="365" alt="スクリーンショット 2015-12-27 13.50.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/62451/983b70aa-2631-38c6-4a51-21e008314aab.png">

しっかりCornerRadiusが反映されてますね。
コードが少し複雑になりましたが、この方法ならボタンのCornerRadiusに合わせて見え方も自然になります。

これなら透過性があるUIView系サブクラスに影を付けることができますし、コンテンツを透明にすれば**任意の場所に自由な影の描画**も可能になります

# おわりに
今回は

- UIView系サブクラスの透過性と影の関係性について
- 透過性があるコンテンツに影を設定する方法

について紹介しました。

なお、コンテンツの影の描画はmasksToBoundsがfalseになっている必要があります。(※masksToBoundsプロパティについては[UIViewのサブクラスによってmasksToBoundsの初期値が異なる](http://qiita.com/shoheiyokoyama/items/c466383464d4b8f02936)参照)

今回はUIButtonの例で紹介しましたが、UILabelやUIImageViewなどのUIView系のコンテンツ全てに当てはまるので、色々活用できると思います。