UIViewにドロップシャドウを追加する最良の方法は何ですか


108

互いに重なっているビューにドロップシャドウを追加しようとしています。ビューが折りたたまれて、他のビューのコンテンツが表示されます。このview.clipsToBoundsため、ビューを折りたたむとコンテンツがクリップされるように、オンのままにしておきます。

これにより、レイヤーにドロップシャドウを追加することが難しくなったようですclipsToBounds。オンにすると、シャドウもクリップされるためです。

私が操作しようとしているview.frameview.bounds私はこれとは運を持っていなかったが、フレームにドロップシャドウを追加しますが、境界は、それを包囲するのに十分大きなことを可能にするために。

シャドウを追加するために使用しているコードは次のとおりです(これはclipsToBounds図のようにOFFでのみ機能します)

view.clipsToBounds = NO;
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(0,5);
view.layer.shadowOpacity = 0.5;

以下は、一番上の灰色のレイヤーに適用されている影のスクリーンショットです。うまくいけば、clipsToBoundsOFFの場合にコンテンツがどのように重複するかがわかります。

シャドウアプリケーション。

シャドウを追加しUIViewてコンテンツをクリップしたままにするにはどうすればよいですか?

編集:シャドウをオンにした背景画像の使用も試してみましたが、これはうまく機能しますが、これに最適なコード化されたソリューションを知りたいと思っています。

回答:


280

これを試して:

UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;

まず第一にUIBezierPathとして使用するshadowPath非常に重要です。それを使用しない場合、最初は違いに気付かないかもしれませんが、鋭い目は、デバイスの回転などのイベント中に発生する特定の遅延を観察します。これは重要なパフォーマンスの調整です。

特にあなたの問題に関して:重要なラインはview.layer.masksToBounds = NOです。これは、ビューの境界よりも遠くに広がるビューのレイヤーのサブレイヤーのクリッピングを無効にします。

masksToBounds(レイヤー上)とビュー自体のclipToBoundsプロパティの違いは何なのかと思っている人のために:実際には何もありません。一方を切り替えると、もう一方にも影響があります。抽象化のレベルが異なるだけです。


Swift 2.2:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.blackColor().CGColor
    layer.shadowOffset = CGSizeMake(0.0, 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.CGPath
}

スウィフト3:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.cgPath
}

1
おかげで、私はあなたのコードを試し、それからmasksToBounds = NO;私のオリジナルに追加しようとしました-両方の試みで私はclipsToBounds = YES;オンに保ちました-どちらもコンテンツをクリップできませんでした。例で何が起こったかのスクリーン
キャップを

1
ドロップシャドウに角がまだ正方形であるかのようにレンダリングするのではなく、丸い角をハグする方法についてのアイデアはありますか?
John Erck、2014年

7
@JohnErckはこれを試してください:UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];。テストされていませんが、希望する結果が得られるはずです。
pkluz 2014年

1
ビューをアニメーション化すると、影が移動中に灰色の軌跡を残すことを学びました。shadowPath行を削除することでこれを解決しました
ishahak

1
@shoeは、ビューの時間サイズが変更されるため、layoutSubviews()呼び出されます。そしてshadowPath、現在のサイズを反映するようにを調整することでその場所を補正しないと、ビューのサイズが変更されますが、影は古い(初期)サイズのままであり、表示すべきではない場所を表示または覗き見できません。
pkluz 2017

65

Swift 2.3におけるWasabiiの回答:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.CGPath

そしてSwift 3/4/5では:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.cgPath

AutoLayoutを使用している場合は、このコードをlayoutSubviews()に配置します。

SwiftUIでは、これははるかに簡単です。

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)

11
注意していただきありがとうございますlayoutSubviews
Islam Q.

1
またはviewDidLayoutSubviews()でさらに良い
Max MacLeod 2015

1
すなわち、変異体、メイクの上に迅速な構造体初期化子を好むCGSize(width: 0, height: 0.5)
オリバー・アトキンソン

このコードをlayoutSubviews()に追加しましたが、それでもIBでレンダリングされませんでした。オプトインする必要がある他の設定はありますか?
ロバ

ありがとう。私は自動レイアウトを使用していて、自分のことを考えていました。まあ、view.bounds作成されていないのでshadowPath、ビューの四角形を参照することはできませんが、CGRectZero代わりにいくつかを参照することはできません。とにかく、これを下に置くとlayoutSubviews、ついに私の問題が解決しました!
devdc 2016年

13

秘訣はmasksToBounds、ビューのレイヤーのプロパティを適切に定義することです。

view.layer.masksToBounds = NO;

そしてそれはうまくいくはずです。

出典


13

UIViewの拡張機能を作成して、デザインエディターでこれらの値にアクセスできます。

デザインエディターのシャドウオプション

extension UIView{

    @IBInspectable var shadowOffset: CGSize{
        get{
            return self.layer.shadowOffset
        }
        set{
            self.layer.shadowOffset = newValue
        }
    }

    @IBInspectable var shadowColor: UIColor{
        get{
            return UIColor(cgColor: self.layer.shadowColor!)
        }
        set{
            self.layer.shadowColor = newValue.cgColor
        }
    }

    @IBInspectable var shadowRadius: CGFloat{
        get{
            return self.layer.shadowRadius
        }
        set{
            self.layer.shadowRadius = newValue
        }
    }

    @IBInspectable var shadowOpacity: Float{
        get{
            return self.layer.shadowOpacity
        }
        set{
            self.layer.shadowOpacity = newValue
        }
    }
}

インターフェイスビルダーでその拡張機能を参照する方法
Amr Angry

IBInspectableは、インターフェイスビルダーで使用できるようにします
Nitesh

Objective Cでもこれを実現できますか?
Harish Pathak

2
(インターフェースビルダーで)リアルタイムのプレビューが必要な場合は、それに関する良い記事を見つけることができます spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill

7

ストーリーボードからビューに影を設定することもできます

ここに画像の説明を入力してください


2

viewWillLayoutSubviews:

override func viewWillLayoutSubviews() {
    sampleView.layer.masksToBounds =  false
    sampleView.layer.shadowColor = UIColor.darkGrayColor().CGColor;
    sampleView.layer.shadowOffset = CGSizeMake(2.0, 2.0)
    sampleView.layer.shadowOpacity = 1.0
}

UIViewの拡張機能を使用:

extension UIView {

    func addDropShadowToView(targetView:UIView? ){
        targetView!.layer.masksToBounds =  false
        targetView!.layer.shadowColor = UIColor.darkGrayColor().CGColor;
        targetView!.layer.shadowOffset = CGSizeMake(2.0, 2.0)
        targetView!.layer.shadowOpacity = 1.0
    }
}

使用法:

sampleView.addDropShadowToView(sampleView)

1
targetView: UIView前髪を強制して削除するだけです。
bluebamboo 2016年

6
なぜ使用しないのselfですか?私にはずっと便利に思えます。
LinusGeffarth

3
これを行うためのより良い方法は、パラメーターとしてtargetViewを削除し、targetViewの代わりにselfを使用することです。これにより、冗長性がなくなり、次のように呼び出すことができます。sampleView.addDropShadowToView()
maxcodes

Max Nelsonのコメントは素晴らしい。私の提案は、if let代わりに構文を使用することです!
ジョニー

0

だから、はい、あなたはパフォーマンスのためにshadowPathプロパティを好むべきですが、また:CALayer.shadowPathのヘッダーファイルから

このプロパティを明示的に使用してパスを指定すると、通常、*複数のレイヤーで同じパス参照を共有するため、レンダリングのパフォーマンスが向上します。

あまり知られていないトリックは、複数のレイヤー間で同じ参照を共有することです。もちろん、同じ形状を使用する必要がありますが、これはテーブル/コレクションビューのセルでは一般的です。

インスタンスを共有した方が高速になる理由はわかりません。シャドウのレンダリングをキャッシュし、ビュー内の他のインスタンスに再利用できると思います。これでもっと速いのかな

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.