27
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-12-28

はじめに

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

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

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

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

透過性と影の描画の関係について

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

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

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

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

透過性の有るコンテンツに影を描画する方法

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

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

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
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;
スクリーンショット 2015-12-27 12.44.41.png

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

スクリーンショット 2015-12-27 12.31.48.png 同じ透過度の場合と比較すると、しっかり影の描画がされているのがわかりますね。

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

( 参照:uiview drop shadow transparency issue )

2.UIBezierPathでpathを自作して影を設定する

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

2.1 Frameの周りのみ影を設定する

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

スクリーンショット 2015-12-27 13.30.08.png
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
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;
スクリーンショット 2015-12-27 13.32.38.png

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

2.2 CornerRadiusを考慮して影を設定する

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

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
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;
スクリーンショット 2015-12-27 13.50.14.png

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

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

おわりに

今回は

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

について紹介しました。

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

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

27
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?