SwiftUI2.0で追加されたAPI
SwiftUI 2.0 で追加されたPageTabViewStyle
を使ってみたかったので、カスタムタブのようなものを作ってみました。
とりあえずの完成形
こういったものを作っていこうと思います。
GitHubはこちらです。
https://github.com/hoshi005/custom-tab
開発環境
- Xcode 12.1
- iOS 14.1
事前準備
- アニメーション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」を簡単に作成できるのは嬉しいですね。
こちらの記事で作った切替ビューでも同じようなことができそうです。もしよかったら試してみてください。