🌊
SwiftUI導入 - TabBar
この記事は?
SwiftUIを導入する話とそれを基に得た知見などをまとめていきます。
SwiftUIの発表から約3年が経ちそろそろSwiftUIを導入しなきゃな〜とお考えの皆さんの参考になったら幸いです!
TabBar
TabBarは、モバイルアプリの画面遷移に使われるビューの一種です。一般的に、アプリ内の主要な画面や機能を表すタブが配置されています。タブをタップすると、対応する画面が表示されます。
TabBarは、通常、画面下部に表示され、アプリのロゴやアイコンとともに表示されます。タブアイコンは、タップすると対応するビューが表示されます。TabBarは、多くの場合、アプリのユーザビリティを向上させるために使用されます。ユーザーは、アプリ内で自由に画面を切り替えることができ、アプリの機能やコンテンツを簡単に見つけることができます。
基本的なTabView
TabViewの実装はとてもシンプルです。
下記はよくあるTabViewの実装です。
Tabを選択した場合のコンテンツ、tabItemの定義、badge情報、これだけ一般的なTabViewの動作が実現できます。
struct TabBarView: View {
var body: some View {
TabView {
Text("First Tab")
.tabItem {
Image(systemName: "1.circle")
Text("First")
}
Text("Second Tab")
.tabItem {
Image(systemName: "2.circle")
Text("Second")
}
.badge(5)
Text("Third Tab")
.tabItem {
Image(systemName: "3.circle")
Text("Third")
}
.badge("New")
}
}
}
カスタムしたTabView
それでは少しカスタムを加えてみましょう
大きく下記2点の変更を加えてみました。
- スワイプでのTab切り替え
- Tabの一つをUIViewContlloerへ変更
struct CustomTabBarView: View {
@State private var selectedTab = 0
var body: some View {
ZStack(alignment: .bottom) {
TabView(selection: $selectedTab) {
Text("First Tab Content")
.tag(0)
Text("Second Tab Content")
.tag(1)
UIViewControllerWrapper(uiViewController: ThirdViewController())
.tabItem {
Image(systemName: "person.fill")
Text("Profile")
}
.tag(2)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.accentColor(.blue)
TabBar(selectedTab: $selectedTab)
.alignmentGuide(.bottom, computeValue: { dimension in
dimension[.bottom]
})
}.frame(width:375,height: 600)
}
}
struct TabBar: View {
@Binding var selectedTab: Int
var body: some View {
HStack {
TabBarItem(imageName: "house.fill", text: "Home", tag: 0, selectedTab: $selectedTab)
TabBarItem(imageName: "list.bullet", text: "List", tag: 1, selectedTab: $selectedTab)
TabBarItem(imageName: "person.fill", text: "Profile", tag: 2, selectedTab: $selectedTab)
}
.background(Color.white)
.padding()
}
}
struct TabBarItem: View {
let imageName: String
let text: String
let tag: Int
@Binding var selectedTab: Int
var body: some View {
Button(action: {
selectedTab = tag
}) {
VStack {
ZStack {
Image(systemName: imageName)
.font(.system(size: 24))
Badge(number: 1)
.offset(x: 10, y: -10)
}
Text(text)
.font(.footnote)
}
.foregroundColor(tag == selectedTab ? .blue : .gray)
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
}
}
}
struct Badge: View {
let number: Int
var body: some View {
Text("\(number)")
.font(.system(size: 12))
.foregroundColor(.white)
.padding(.horizontal, 4)
.padding(.vertical, 2)
.background(Color.red)
.clipShape(Circle())
}
}
struct UIViewControllerWrapper: UIViewControllerRepresentable {
let uiViewController: UIViewController
func makeUIViewController(context: Context) -> UIViewController {
return uiViewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
class ThirdViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Add a label to the view controller's view
let label = UILabel(frame: CGRect(x: view.bounds.width / 2, y: view.bounds.height / 2, width: 200, height: 50))
label.center = view.center
label.textAlignment = .center
label.text = "Third Tab Content"
view.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate(
[
view.topAnchor.constraint(equalTo: label.topAnchor),
view.bottomAnchor.constraint(equalTo: label.bottomAnchor),
view.leadingAnchor.constraint(equalTo: label.leadingAnchor),
view.trailingAnchor.constraint(equalTo: label.trailingAnchor)
]
)
}
}
ご覧の通りコード量がかなり多くなります。
特に tabViewStyle
indexViewStyle
Badge
のカスタムをする必要性が出てきたりTabBarItemとTabViewの分離の連携が必要になります。このように少しカスタムするだけで無理な実装が入るので、SwiftUIの挙動を意識した画面設計が必要になります。
Discussion