【SwiftUI】リバースマスクを実装し、Viewを切り抜く方法

2022.10.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

以前、UIViewで切り抜く方法は紹介したのですが、せっかくなのでSwitUIViewでも切り抜く方法を調べてみました。

【Swift】UIViewを切り抜く方法

環境

  • Xcode 14

Viewをマスク

図形で切り抜き

SwiftUIでクリッピングマスクを行うのは非常に簡単です。

図形で切り取りを行いたい場合は、.clipShapeを使用します

Image("img_mask")
    .resizable()
    .frame(width: 300, height: 300)
    .clipShape(Circle())

Viewで切り抜き

Shape型だけではなく、特定のViewで切り抜くことも出来ます。

Image("img_mask")
    .resizable()
    .frame(width: 300, height: 300)
    .mask {
        Circle()
    }

Viewの中を切り抜く

SwiftUIにはリバースマスクのAPIがまだ無い為、Viewの中を切り抜くにはまだ一手間必要です。

マスクの原理

簡単な説明になりますが、今回のCircleで切り抜く例で言いますと、.maskに記述したViewと重なっている箇所だけが描画されるという動きになっています。

重なっている箇所 mask後

なので、リバースマスクを表現する為には、切り抜きたい箇所が切り抜かれたViewが必要です。

切り抜かれたViewを作成

.blendMode(.destinationOut)を使用して合成した切り抜かれたViewを作成します。

.blendModeは、Viewとその重なり合うViewとを合成するためのモードを設定します。

.destinationOutを設定すると、重なり合っている部分だけを切り抜き、切り抜かれた状態のものが残ります。また、compositingGroup()を使用して合成効果を有効にする為にラップしています。

Zstackを使用した例

ZStack {
    Rectangle()

    Circle()
        .blendMode(.destinationOut)
}
.compositingGroup()
.background(.mint)

overlayを使用した例

Rectangle()
    .fill(.black)
    .overlay() {
          Circle()
            .blendMode(.destinationOut)
        }
    .compositingGroup()
    .background(.mint)

プレビュー

円の形が切り抜かれたViewが完成しました。しっかり背景の色が切り抜かれた箇所から確認が出来ます。

切り抜かれたViewでマスクする

.maskの中に作成した切り抜かれたViewを渡してみましょう。

Image("img_mask")
    .resizable()
    .frame(width: 300, height: 300)
    .mask {
        // 元々切り抜かれたView
        Rectangle()
            .overlay() {
                Circle()
                    .blendMode(.destinationOut)
            }
            .compositingGroup()
    }

プレビュー

Viewの中が円形に切り抜かれて出力出来ました!

Extensionを作成

汎用的に使用する為のExtensionを作成しました。

extension View {

    func reverseMask<Mask: View>(alignment: Alignment = .center,
                                 @ViewBuilder _ mask: () -> Mask) -> some View {
        self.mask {
            Rectangle()
                .overlay(alignment: alignment) {
                    mask()
                        .blendMode(.destinationOut)
                }
               // .compositingGroup() 無くても機能する
        }
    }
}

使用例

Image("img_mask")
    .resizable()
    .frame(width: 300, height: 300)
    .reverseMask {
        Image(systemName: "applelogo")
            .font(.system(size: 100))
    }

プレビュー

おしゃれな感じに仕上がりました!

おわりに

SwiftUIでもViewを切り抜く表現をすることが出来ました!今後のSwiftUIのバージョンアップで手軽に実装出来るようになるとさらに良いですね! この切り抜きを活用した何かを作りたくなってきたので、また試したら記事にしてみようと思います。

素敵な切り抜きライフをお過ごしください?

参考