SwiftUI 2.0 でカスタムタブビューを作ってみた
SwiftUI 2.0で追加されたAPI
SwiftUI 2.0 で追加されたPageTabViewStyle
を使ってみたかったので、カスタムタブのようなものを作ってみました。
とりあえずの完成形
こういったものを作っていこうと思います。
GitHubはこちらです。 github.com
開発環境
参考書
SwiftUIの勉強には、この辺りの本が特におすすめです!
事前準備
- アニメーションgifを利用したかったのでSDWebImageSwiftUIを利用しています。
- ぴよたそさんからアニメーションgifファイルを利用させてもらっています。
タブ部分の作成
アニメーションgifファイルはこのように名前をつけて配置したので、名前を合わせる形でenumを定義しています。
enum TabItem: String, CaseIterable { case piyo case pen case neko case tobipen var name: String { "\(self.rawValue).gif" } }
タブの一つ一つを表すためのTabItemView
を追加して、以下のように定義しました。
struct TabItemView: View { let tabItem: TabItem @Binding var selected: TabItem var body: some View { // SDWebImageSwiftUIのimportが必要. AnimatedImage(name: tabItem.name) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 40) .onTapGesture { selected = tabItem // タップしたら自身をselectedに. } } }
メインとなるContentView
には、以下のようにタブビューを定義しました。
struct ContentView: View { // タブの選択値と初期値. @State private var selected: TabItem = .piyo var body: some View { // タブビュー部分. HStack { ForEach(TabItem.allCases, id: \.self) { tabItem in TabItemView(tabItem: tabItem, selected: $selected) } } .padding(.vertical, 10.0) .padding(.horizontal, 20.0) .background(Color.white.clipShape(Capsule())) .shadow(color: Color.black.opacity(0.3), radius: 5, x: -5, y: 5) } }
出来上がったのはこちらです。このままだと、選択状態がよくわからないですね。
選択状態がわかるように見た目を調整する
選択時/非選択時で見た目を切り替えるため、TabItemView
を以下のように書き換えます。
- frameを調整
- paddingを調整
- offsetを調整
- タップ時の処理にアニメーションを伴わせる
var body: some View { AnimatedImage(name: tabItem.name) .resizable() .aspectRatio(contentMode: .fit) // 選択状態によって、サイズや間隔を調整する. .frame(width: tabItem == selected ? 100 : 40) .padding(.vertical, tabItem == selected ? -30 : 0) .padding(.horizontal, tabItem == selected ? -14 : 16) .offset(y: tabItem == selected ? -15 : 0) .onTapGesture { withAnimation(.spring()) { selected = tabItem // タップしたら自身をselectedに. } } }
見た目はこのようになります。選択状態が一目でわかるようになりました。
背景色の設定と、タブの配置調整
ContentView
の見た目を調整します。
- 全体を
ZStack
で囲う - 最背面に
Color("bg").ignoresSafeArea()
を配置して背景色とする - タブビュー部分を
VStack
とSpacer
を利用して画面下部に配置
var body: some View { ZStack { // 背景色. Color("bg").ignoresSafeArea() VStack { Spacer(minLength: 0) // タブビュー部分. HStack { ForEach(TabItem.allCases, id: \.self) { tabItem in TabItemView(tabItem: tabItem, selected: $selected) } } .padding(.vertical, 10.0) .padding(.horizontal, 20.0) .background(Color.white.clipShape(Capsule())) .shadow(color: Color.black.opacity(0.3), radius: 5, x: -5, y: 5) } } }
見た目はこうなりました。それっぽくなってきましたね。
画面をタブで切り替える
タブは用意したので、このタブに連動して画面が切り替わるようにします。
まずはダミーで画面部分を用意します。
適当なので、こちらは好きに作ってもらって良いと思います。
struct HomeView: View { var body: some View { Text("Home") .font(.largeTitle) .fontWeight(.heavy) .foregroundColor(.red) } } struct ListView: View { var body: some View { Text("List") .font(.largeTitle) .fontWeight(.heavy) .foregroundColor(.green) } } struct SearchView: View { var body: some View { Text("Search") .font(.largeTitle) .fontWeight(.heavy) .foregroundColor(.blue) } } struct SettingView: View { var body: some View { Text("Setting") .font(.largeTitle) .fontWeight(.heavy) .foregroundColor(.yellow) } }
最後に、これらのViewをTabView
で定義し、カスタムタブと連動するようにします。
TabView
の引数にselected
を指定することで、カスタムタブと連動させるPageTabViewStyle
を指定することで、横スワイプでの切り替えを可能にする
ZStack { // 背景色. Color("bg").ignoresSafeArea() // メイン画面部分はTabViewで定義. TabView(selection: $selected) { HomeView() .tag(TabItem.piyo) ListView() .tag(TabItem.pen) SearchView() .tag(TabItem.neko) SettingView() .tag(TabItem.tobipen) } // PageTabスタイルを利用する(インジケータは非表示). .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) VStack { // 省略. } }
まとめ
タブの定義がずいぶん簡単にできる印象ですが、それ以上に「切り替え用のUI」を簡単に作成できるのは嬉しいですね。
こちらの記事で作った切替ビューでも同じようなことができそうです。もしよかったら試してみてください。 hoshi0523.hatenablog.com