23
18

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 3 years have passed since last update.

[Swift] SwiftUI で画面のくり抜きを実現する

Posted at

前置き

ユーザーにヒントを出す画面は多くのアプリで実装されているかと思います。

img.png

この画面は実際にSwiftUIで作られています。
では、どのように行ったのかをみていきましょう。

実装方法

2パターン紹介したいと思います。

1. UIBezierPath on SwiftUI で実装

UIKitの場合はUIBezierPathを使った実装方法がありました。
以下はObjective-Cのコードなので古いですが、比較的にわかりやすいコード例です。

UIBezierPath で「画面全体」と「くり抜く部分」を重ねることで、重なった部分のみ透過できるというやり方です。これを SwiftUIView に適応するだけです。

今回は、以下のような少し簡単にした画面で紹介していきます。

くり抜き前 くり抜き後
before.png after.png

画面構成

画面自体は、以下の3つを組み合わせたとてもシンプルな構成になっています。

①アラート画面 ②黒いフィルター画面 ③透明な穴の画面

① アラート画面

.swift
struct TutorialAlertView: View {

    var handler: (() -> Void)?

    var body: some View {
        VStack {
            Text("Title")
                .padding(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0))

            Text("Description")
                .padding(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0))

            Button(action: { handler?() }, label: {
                Text("OK")
                    .frame(width: 252, height: 44)
                    .foregroundColor(.white)
                    .background(Color.red)
                    .cornerRadius(4)
                    .padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
            })
            .contentShape(Rectangle())
        }
        .frame(maxWidth: 300)
        .background(Color.white)
        .cornerRadius(4)
    }
}

② 黒いフィルター画面

.swift
struct TutorialFilterView: View {

    var body: some View {
        Color.black
            .opacity(0.7)
            .mask(TutorialHoleView())
            .edgesIgnoringSafeArea(.all)
    }
}

mask()を使うことでフィルター部分の切り抜きを行なっています。

③ 透明な穴の画面

.swift
struct TutorialHoleView: View {

    var body: some View {
        // 穴
        let holePath = UIBezierPath(roundedRect: CGRect(
            x: 8,
            y: 60,
            width: 100,
            height: 80
        ), cornerRadius: 24)

        // 全体
        var shape = Path(CGRect(
            origin: .zero,
            size: UIScreen.main.bounds.size
        ))
        shape.addPath(Path(holePath.cgPath)) // Pathに変換
        return shape.fill(style: FillStyle(eoFill: true))
    }
}

SwiftUIではPathというものがあり、UIBezierPathから変換することで扱うことができます。変換自体は.cgPathを参照するだけで可能なのでとても簡単です。FillStyleがないと透過部分が反転してしまうのでつけてください。

①~③を組み合わせた画面

これをZStackで重ねて組み合わせると完成します。

画面 重なり
1.png 2.png
.swift
struct TutorialView: View {

    var body: some View {
        ZStack {
            TutorialFilterView()
            TutorialAlertView { /* do some button action */ }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

今回は例なのでframeを固定にしていますが、位置を調整する場合は前の画面から渡して行く必要があります。

2. SwiftUI のみで実装

先ほど実装した、UIBezierPathで実装した部分をSwiftUIShapeを使って実装します。

基本的にはほぼ同じで、「③ 透明な穴の画面」のコードで作成したTutorialHoleViewの部分を、以下のようにShape(Rectangle)にします。

example code

.swift
struct TutorialHoleView: View {

    var body: some View {
        Rectangle()
            .cornerRadius(24)
            .frame(width: 100, height: 80)
            .position(x: 60, y: 150)
            .background(Color.white)
            .compositingGroup() // ※1
            .luminanceToAlpha() // ※2
    }
}

Shape を使う場合は、注意点として以下の2つを設定する必要があります。

  • compositingGroup() : ZStack の中身をグループ化する
  • luminanceToAlpha() : 暗い領域は透明に、明るい領域は不透明な黒にする

これを設定することで、くり抜きを反映することができます。


また、Rectangleを使用していますが、Shapeであれば良いので、

  • RotatedShape
  • RoundedRectangle
  • Circle

など、さまざまな図形を用いて、くり抜くことができます。

組み合わせた画面

画面 重なり
1.png 2.png

このようにSwiftUIが用意した図形を用いることで、シンプルな実装にすることができます。

使い分け

簡単なくり抜きの実装であれば、SwiftUIShapeを使った実装の方が楽に実装可能です。ただ、星形やハートなど、難しい図形をくり抜きたい場合は、Path or UIBezierPathで図形を作成することになります。

動作するリポジトリを置いておくので、いろいろ試してみると面白いかと思います。

その他

  • 「1. UIBezierPath on SwiftUI で実装」の実装で参考になったもの

  • 「2. SwiftUI のみで実装」の実装で参考になったもの

  • UIBezierPathの方法を紹介しましたが、Pathで図形を作る方法も掲載しておきます

23
18
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
23
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?