0
2

More than 1 year has passed since last update.

【SwiftUI】onDisappearで動的にページを再生成して無限に横ページ移動

Posted at

内容

定数個のViewを横に並べたカルーセルの記事はよく拝見しましたが、動的に前ページ、現在ページ、次ページを作り替えていくパターンはぱっと見つからなかったのでやってみました。
移動アニメーションが終わった後に、移動前のページを新しい現在ページとするにはアニメーション終了検知が必要ですが、自分のスキル的にまだ難しそうだったので、onDisappearを利用して近いことを実現しました。
軽い頭の体操くらいの気持ちで見てもらえればと思います。自分はこれをやったことでStateと再描画の理解が深まりました。

開発環境

macOS Monterey v.12.5
Xcode v.13.4.1
主にiPhoneSE(3rd)(iOS15.5)のシミュレーターを利用

結果gifとコード

git_move_page.gif
タップするごとに無限に1ページずつ減っていきます。
最初に用意しているのは5,6,7ページのみで、onTapGestureを実装しているのは現在ページのみです。タップのたびに「横移動->終了後に1ページずらして各ページ再生成」をやってます。

実装の大きな流れは

  • page5(前ページ), page6(現在ページ), page7(次ページ)を作ってHStackで横に並べる
  • 現在ページのonTapGesture@Stateのphaseが変更される
    • HStack全体のoffsetが更新されて前ページに移動する(ように見える。実際は全体が右に動く)
    • これだけでは移動して前ページが画面範囲に入っただけで、現在ページという扱いではなく、タップで前ページに移動することはできない
      • そこで@Stateのphaseが変わるとonDisappearが発火するようダミーViewを配置しておき、そこでページ生成元の@Stateのpagesを更新し、前ページの内容が現在ページになるよう再描画

コードです↓

import SwiftUI
struct ContentView: View {
    @State var phase:String = "init"
    @State var pages:[Int] = [5,6,7]
    var body: some View {
        return (
            HStack(spacing:0){
                // prev page
                VStack{
                    Text("page \(pages[0])")
                    Text("タップして前ページへ").foregroundColor(Color.blue)
                }.frame(width: UIScreen.main.bounds.width)

                // dummy
                if phase=="init"{
                    // initではなくなった時にonDisappearを走らせるためのダミーView
                    ZStack{
                        Text("")
                    }.onDisappear{
                        // 移動完了と同じタイミングでstateを更新して、prev pageの内容をcurrent pageに割り当て直す
                        pages = [pages[0]-1, pages[1]-1, pages[2]-1]
                        phase = "init"
                    }
                }
                
                // current page
                VStack{
                    Text("page \(pages[1])")
                    Text("タップして前ページへ")
                        .foregroundColor(Color.blue)
                        .onTapGesture{
                            withAnimation(.linear(duration: 1)){
                                phase = "tapped"
                            }
                        }
                }.frame(width: UIScreen.main.bounds.width)
                
                // next page
                ZStack{
                    Text("page \(pages[2])")
                    Text("タップして前ページへ").foregroundColor(Color.blue)
                }.frame(width: UIScreen.main.bounds.width)
            }
            .offset(x: phase=="init" ? 0 : UIScreen.main.bounds.width)  // tappedに変わったときに全体を動かしてページ移動
        )
    }
}

おわり

0
2
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
0
2