It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【SwiftUI】フェードアニメーションで画面遷移を行う

概要

SwiftUIで画面遷移をする方法の1つとして「表示フラグを使用して遷移をコントロールする」というものがあります。

詳しくは、以前書いた記事を読んでいただければ幸いです。

inon29.hateblo.jp

この方法を使う場合は、画面遷移のアニメーションは自分で書く必要があります。
今回は画面遷移に使われるようなアニメーションの基礎を理解するためにフェードアニメーションによる画面遷移を実際に書いて試してみました。

環境

  • Swift: 5.2.2
  • Xcode: 11.4.1

遷移元の画面

f:id:inon29:20200516120243p:plain

struct FirstView: View {
    @State var isPresented = false
    var body: some View {
        ZStack {
            VStack {
                Button("Show FadeView") {
                    self.isPresented.toggle()
                }
            }
            // NOTE: Boolのフラグで表示非表示を分ける
            if isPresented {
                FadeView(isPresented: $isPresented)
            }
        }
    }
}

対象の画面を呼び出す元画面の実装です。
フェードアニメーションで表示する画面 FadeViewをボタン押下で表示します。
表示制御は、@StateのBoolの値(isPresented)をボタン押下時に変更することで行っており、isPresentedがtrueなら FadeView を描画するように制御しています。
isPresentedをFadeViewに渡しているのは、画面を閉じる制御をFadeView上で行うためです。
(詳細は後述するFadeViewの実装をみてください)

フェードアニメーションで表示する画面

f:id:inon29:20200516120314p:plain

struct FadeView: View {
    @State var opacity: Double = 0
    @Binding var isPresented: Bool
    
    var body: some View {
        ZStack(alignment: .topLeading) {
            VStack {
                Text("SecondView")
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            Button(action: {
                withAnimation(.linear(duration: 0.3)) {
                    self.isPresented = false
                }
            }) {
                Text("閉じる")
                    .foregroundColor(Color.white)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.blue)
        .opacity(self.opacity)
        .onAppear {
            withAnimation(.linear(duration: 0.3)) {
                // NOTE: opacityを変更する画面再描画に対してアニメーションを行う
                self.opacity = 1.0
            }
        }
    }
}

フェードアニメーションを行う画面(FadeView)の実装です。

左上の閉じるボタンを押下することで画面を閉じることができます

画面表示のフェードインアニメーション

.onAppear {
  withAnimation(.linear(duration: 0.3)) {
    self.opacity = 1.0
  }
}

画面表示のフェードインアニメーションは、画面(ZStack)の onAppear に記述しています。
アニメーションには、SwiftUIでアニメーションを実装するときに便利な関数である withAnimation を使っています。

withAnimation は引数のクロージャ内で発生した画面描画に対してアニメーションを付けてくれる関数です。

上記処理では画面の透明度(opacity)を 0.0 => 1.0 に変更する処理をonAppearで行っており、この処理での再描画にアニメーションをつけることでフェードインアニメーションを適用しています。

withAnimation には、アニメーションの種類を引数として渡すことができます。
渡せるアニメーションの種類には、各種イージングなど様々なものが用意されていますが、今回は linear アニメーションを使用しています。
アニメーションによって実行時間も指定できます。
(今回は0.3秒でアニメーションしています。)

画面非表示のフェードアウトアニメーション

Button(action: {
    withAnimation(.linear(duration: 0.3)) {
        self.isPresented = false
    }
}) {
    Text("閉じる")
        .foregroundColor(Color.white)
}

画面を閉じる時のフェードアウトアニメーションです。
呼び出し画面から渡された isPresented フラグをfalseにすることで親画面が再描画され、FadeViewは非表示になります。
この際に画面表示と同様に withAnimation 関数を使用することでフェードアウトのアニメーションを適用することができます。
アニメーションの種類については、画面表示と同様のため説明は割愛します。

まとめ

SwiftUIでは、それほど複雑なアニメーションでなければシンプルにアニメーションを記述することができます。
フラグで画面遷移を実装することはNavigationLinkなどと比べると手間はかかりますが、アニメーションをカスタマイズできるという面では使い勝手は自由なので面倒くさがらず使っていこうと思います。