20
17

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のTabViewの問題に自作TabView(SwiftUI製)で対処する

Last updated at Posted at 2019-11-26

追記(20200623)

XCode12 + iOS14 からTabViewの挙動が変更されて、タブを切り替えても前のViewが保持されるようになりましたのでiOS14以降ならこの記事はあまり意味がなくなりましたw

SwiftUIのTabViewの問題に自作TabView(SwiftUI製)で対処する

 

SwiftUIのTabViewには、Tabを切り替えると切り替えた先のタブを再生成するためスクロールした位置や状態を保持しておくことができませんでした。

それに対処するために、今回自作TabViewを作成しましたので、その tipsを共有します。

作成したTabViewを見たい方はこちらから!
https://github.com/tsuzukihashi/SwiftUI_CustomTabBar

また、UIKitのUITabBarControllerを Representaぶる方法もあります。そっちの方がiOS的には優しいかもしてません...

こっちのメリットは、タブ切り替えのアニメーションがいじりやすいのと、オールSwiftUIのモチベぐらいでしょうか...

間違いやアドバイスがあればじゃんじゃんコメントください!お願い致します🙇‍♂️

Twitter やっています!
https://twitter.com/tsuzuki817

色々アプリをリリースしているので一度見てみてください🙇‍♂️
https://apps.apple.com/jp/developer/ryo-tsudukihashi/id1320583602?l

現状の問題を把握

先ほど説明しましたが、改めてGIFアニメーション載せておきます。
2tdvx-fuo22.gif

自作TabViewへの道

大まかな流れは以下のようになっています。

  • ステップ1
    ルートのViewで現在選択しているcurrentPageを保持する
  • ステップ2
    ルートのViewでタブで遷移したいViewをZStackで並べる
  • ステップ3
    ステップ2で配置したViewのopacityをcurrentPageによって変える
  • ステップ4
    タブもどきに設置したボタンでcurrentPageを切り替える

説明しやすいように、シンプルなアプリを作成します。

表示させたいView達

変化が分かりやすいように縦に多くスクロールできるViewを作成しています。

Page2では天下のイラストや様の画像を少しばかりお借りしております。

import SwiftUI
struct Page1: View {
    var body: some View {
        NavigationView {
            VStack {
                List(0..<30) { i in
                    Text("\(i)列目")
                }
            }.navigationBarTitle("1ページ目")
        }
    }
}

struct Page2: View {
    var body: some View {
        NavigationView {
            ScrollView(.vertical, showsIndicators: false) {
                ForEach(1..<8) { i in
                    Image("irasutoya\(i)")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                }
            }.navigationBarTitle("2ページ目")
        }
    }
}

struct Page3: View {
    var body: some View {
        NavigationView {
            VStack {
                List(0..<30) { i in
                    Text("\(i)列目")
                }
            }.navigationBarTitle("3ページ目")
        }
    }
}

ルートのView

このViewで@State で currentPageを保持しておきます。
そのcurrentPageの値を見てViewのopacityを変更して、表示・非表示を切り替えています。

また、ここでoffsetをいじっているのですが、これはアニメーションのために利用しています。

そのためアニメーションを利用しない方は、なくてもおkです!
(.rotation3DEffectとか使ったらリッチになって面白そうだったのですが、navigationbar が少しおかしくなりました;;)

import SwiftUI

struct ContentView: View {
    @State  var currentPage = 1
    
    var body: some View {
        ZStack {
            Page1()
                .opacity(currentPage == 1 ? 1 : 0)
                .offset(x : currentPage == 1 ? 0 : 100)
                .animation(Animation.spring())
            
            Page2()
                .opacity(currentPage == 2 ? 1 : 0)
                .offset(x : currentPage == 2 ? 0 : 100)
                .animation(Animation.spring())
            
            Page3()
                .opacity(currentPage == 3 ? 1 : 0)
                .offset(x : currentPage == 3 ? 0 : 100)
                .animation(Animation.spring())
            
            MyTabView(currentPage: $currentPage)
        }
    }
}

肝心の自作TabView

@BindingでcurrentPageをルートViewから受け取り、設置するボタンでその値を変更します。

VStack, Spacer() , ZStack, Rectangleを駆使して擬似タブを作成します。

.edgesIgnoringSafeArea(.bottom)を使って、セーフエリアまで覆うのを忘れないようにしておけば、後は結構自由です。

struct MyTabView: View {
    @Binding var currentPage: Int
    var width = UIScreen.main.bounds.width
    var body: some View {
        VStack {
            Spacer()
            ZStack {
                Rectangle()
                    .foregroundColor(Color.gray)
                    .frame(width: width, height: 88)
                
                HStack(spacing: 88) {
                    Button(action: {
                        self.currentPage = 1
                    }) {
                        VStack {
                            Image(systemName: "flame")
                                .foregroundColor(Color.red)
                                .font(.system(size: 32, weight: .bold, design: .rounded))
                            
                        }
                    }.padding(.bottom, 24)
                    
                    Button(action: {
                        self.currentPage = 2
                    }) {
                        VStack {
                            Image(systemName: "bolt")
                                .foregroundColor(Color.yellow)
                                .font(.system(size: 32, weight: .bold, design: .rounded))
                            
                        }
                    }.padding(.bottom, 24)
                    
                    Button(action: {
                        self.currentPage = 3
                    }) {
                        VStack {
                            Image(systemName: "leaf.arrow.circlepath")
                                .foregroundColor(Color.green)
                                .font(.system(size: 32, weight: .bold, design: .rounded))
                            
                        }
                    }.padding(.bottom, 24)
                    
                }
            }
        }.edgesIgnoringSafeArea(.bottom)
    }
}

できたもの

6t67u-qqz8z.gif

タブを切り替えてもViewは作り直されず、遷移前の状態を保持することができました!

最終的な着地点

以上で実装は終わりです!

SwiftUIは色々不便なことも多いですが、楽しいですね!

そのうちTabViewの問題もApple様が直してくれそうですが、一時的な場しのぎとしてどうでしょうか

最後にもう一度作成したリポジトリです!
https://github.com/tsuzukihashi/SwiftUI_CustomTabBar

閲覧ありがとうございましたmm

20
17
1

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
20
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?