0
1

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.

SwiftUIでページ遷移(左部)を実装する

Posted at

参考

【iOS】遷移方法まとめとエンジニア1年生の独り言
上部左右から出現するメニューを作る
SwiftUIのGestureについてまとめ

画面遷移について

  1. モーダル ... 下から画面が出現
  2. プッシュ ... 右から画面が出現
  3. タブ ... 瞬時の切り替わり(タブアイテムのタップ)
  4. ページ ... 左右から画面が出現(左右スワイプ) ※サイドメニューと同義

ページ遷移(左部)の実装

動画

コード

ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var showingModal = false
    
    @State private var vOffset = CGFloat.zero    // 現在のオフセット(y軸方向)
    @State private var vCloseOffset = CGFloat.zero   // 閉状態のオフセット(y軸方向)
    @State private var vOpenOffset = CGFloat.zero    // 開状態のオフセット(y軸方向)
    @State private var hOffset = CGFloat.zero    // 現在のオフセット(x軸方向)
    @State private var hCloseOffset = CGFloat.zero   // 閉状態のオフセット(x軸方向)
    @State private var hOpenOffset = CGFloat.zero    // 開状態のオフセット(x軸方向)
   
    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .leading) {
                // メインコンテンツ
                VStack {
                    Button("Show ModalView.") {
                        self.showingModal.toggle()
                    }.sheet(isPresented: $showingModal) {
                        ModalView()
                    }
                    
                    Divider()
                    
                    NavigationView {
                        NavigationLink(destination: PushView()) {
                            Text("Show PushView.")
                        }.navigationBarTitle("ContentView", displayMode: .automatic)
                    }
                    
                    Divider()
                    
                    TabView {
                        TabAView()
                            .tabItem {
                                Image(systemName: "moon")
                                Text("Tab A")
                            }
                        
                        TabBView()
                            .tabItem {
                                Image(systemName: "moon.fill")
                                Text("Tab B")
                            }
                    }
                }
                // 上部メニュー
                PageUpView()
                    .foregroundColor(Color.red)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    // View描画時に呼び出すメソッド
                    .onAppear(perform: {
                        // PageUpViewのオフセット初期値として負の方向(上方向)に[Safe Areaの高さ]+[PageUpView自体の高さ]分ずらす
                        self.vOffset = -1 * (geometry.frame(in: .global).origin.y + geometry.size.height)
                        // 閉状態のオフセット = 初期状態のオフセット
                        self.vCloseOffset = self.vOffset
                        // 開状態のオフセット = 0
                        self.vOpenOffset = .zero
                    })
                    // オフセット
                    .offset(y: self.vOffset)
                    // アニメーション
                    .animation(.default)
                
                // 左部メニュー
                PageLeftView()
                    .foregroundColor(Color.blue)
                    .frame(width: geometry.size.width)
                    .edgesIgnoringSafeArea(.all)
                    // View描画時に呼び出すメソッド
                    .onAppear(perform: {
                        self.hOffset = -1 * geometry.size.width
                        self.hCloseOffset = self.hOffset
                        self.hOpenOffset = .zero
                    })
                    .offset(x: self.hOffset)
                    .animation(.default)
            }
            //ジェスチャーに関する実装
            .gesture(DragGesture(minimumDistance: 5)
                        // DragGestureの入力開始時の呼び出しメソッド
                        .onChanged { value in
                            // ContentViewのy軸方向の現在のオフセットが初期値でない かつ DragGestureの開始位置のy座標が30未満の場合
                            if (self.vOffset != self.vOpenOffset && value.startLocation.y < 30) {
                                // ContentViewの現在のオフセット = 初期状態からy軸方向にDragされた距離
                                self.vOffset = self.vCloseOffset + value.translation.height
                            }
                            // ContentViewのx軸方向の現在のオフセットが0未満(常に満たす) かつ DragGestureの開始位置のx座標が30未満の場合
                            else if (self.hOffset < self.hOpenOffset && value.startLocation.x < 30) {
                                // ContentViewの現在のオフセット = 初期値からx方向にDragされた距離
                                self.hOffset = self.hCloseOffset + value.translation.width
                            }
                        }
                        // DragGestureの入力終了時の呼び出しメソッド
                        .onEnded { value in
                            //Gesture終了時のViewのy座標がGesture開始時よりも高い(Viewが下方向に動いた場合)
                            if (value.startLocation.y < value.location.y) {
                                // Gestureの開始位置のy座標が30未満の場合
                                if (value.startLocation.y < 30) {
                                    // PageUpViewを開状態にする
                                    self.vOffset = self.vOpenOffset
                                }
                            }
                            // Gesture終了時のViewのx座標がGesture開始時よりも高い(Viewが右方向に動いた場合)
                            else if (value.location.x > value.startLocation.x) {
                                if (value.startLocation.x < 30) {
                                    // PageLeftViewを開状態にする
                                    self.hOffset = self.hOpenOffset
                                }
                            }
                            else {
                                // PageUpView, PageLeftViewを閉状態にする
                                self.hOffset = self.hCloseOffset
                                self.vOffset = self.vCloseOffset
                            }
                        }
            )
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
PageLeftView.swift
import SwiftUI

struct PageLeftView: View {
    var body: some View {
        ZStack {
            Color.blue
            Text("This is Left Side.")
        }
    }
}

struct PageLeftView_Previews: PreviewProvider {
    static var previews: some View {
        PageLeftView()
    }
}

学習結果

オフセット変数

offset変数はContentView自身で管理している、ContentViewのオフセットを指す。
上下左右のサイドメニューを実装する際はx軸/y軸方向それぞれにオフセット変数を設定する必要がある。

不具合

DragGesture時にPageLeftViewが完全な開状態にならず、
スライドした分だけで画面が止まってしまう場合がある。(動画の冒頭参照)
→ 考察: Gesture時に、onChanged修飾子内のself.hOffset = self.hCloseOffset + value.translation.widthが動作している?

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?