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
