SwiftUI の .shadow のパラメータにspreadを生やす
SwiftUI では css でおなじみのshadowの範囲を広げたり狭めたりするパラメータが存在しない。なのでうまく再現できるものを作った。
コード:
上がspreadに-8をいれたもの。下が標準の .shadow 。spread以外のパラメータは同一。
https://scrapbox.io/files/673d6373d35a59bd2e520d72.png
shadowにspreadをつけたいことがままある。というのも影をすこし下側にずらすケースで、浮き感をだすためにすこし影を内側に入りこませたくなる。真下に落とす場合と比べて浮いている雰囲気がより強くなるからだ。
前の例の画像ではspread以外は同じ値を指定していたが、見た目を寄せるために影の半径、影の透明度をいじったものが次の画像。
上: shadow(color: .black.opacity(0.5), x: 0, y: 0, radius: 16, spread: -8)
下: shadow(color: .black.opacity(0.2), x: 0, y: 0, radius: 10)
https://scrapbox.io/files/673d64bc29cd29b66e7081cb.png
やはり後者はベタッと影がついている感じがする。比べて前者は浮いている感じが出ている。浮いてるほど内側に光が入り込んで影が薄くなるといったようなロジックで、立体感をより感じる。どうしてもspread無しでは表現できないものがある。
懸念点はやはり描画コストである。compositingGroup などの指定によって描画のコンポジションのレイヤーを切ったりなど、いくつか最適化をいれてみた。現実的な利用ではおそらく問題にはならないとは思うが、パフォーマンスはビルトインに比べたらどうしても悪くなる。
実装の概要について。
仕組みはわりと単純である。描画されるオブジェクトの形の黒いシェイプを作り、ブラーを掛けて裏に置いてある。
ポイントはmaskを使って黒いシェイプを切り取っているところで、一部の透過系オブジェクト(Material系)では正しくマスクできない問題があった。そこでdrawingGroupを使い描画レイヤーまで処理を運んでからマスク処理に送ることで、透過系に対する問題をクリアしている。
もう一つのポイントはオブジェクトの裏側には黒い影を落とさないように、さらに描画されるオブジェクトの形で影を切り取っている。つまりはみ出て見える影だけを描画しているのだ。これは透過系のオブジェクトが本来の色よりも暗くならないようにするための処理である。一般的なドロップシャドウを調べると概ねこの処理になっているようだ。
SwiftUIは描画のパイプラインに対するコントロールがプログラマブルにできて、 宣言的なcssに比べてかなり柔軟性がある。すこし複雑なエフェクトも作れてしまうところが面白い。