はじめに
カスタムタブを作成した際にoverlayを使って表示するとコンテンツに被ってしまうという問題に当たりました。
safeAreaInsetを使ったところ解決したので、記事にしておきます。
今回のサンプルアプリでは「ホーム」「通知」「プロフィール」のタブがある想定で進めます。
しかし、ちゃんと実装するのは「ホーム」だけで「通知」「プロフィール」にはTextを配置します。
サンプルアプリ
カスタムタブの実装
import SwiftUI
struct CustomTabView: View {
@Binding private var selectedTabItem: TabItem
init(selectedTabItem: Binding<TabItem>) {
self._selectedTabItem = selectedTabItem
}
var body: some View {
HStack(spacing: 0) {
ForEach(TabItem.allCases, id: \.id) { tabItem in
Button {
self.selectedTabItem = tabItem
} label: {
(selectedTabItem == tabItem ? tabItem.activeTabImage : tabItem.inactiveTabImage)
.resizable()
.scaledToFit()
.frame(height: 24)
}
.foregroundStyle(selectedTabItem == tabItem ? Color.primary : Color.secondary)
.frame(width: 55, height: 40)
.frame(maxWidth: tabItem == TabItem.notification ? .infinity : nil)
}
}
.padding(.horizontal, 42)
.padding(.top, 7)
.background(Material.bar)
}
}
enum TabItem: String, CaseIterable, Hashable, Identifiable {
case home
case notification
case profile
var id: String { rawValue }
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue == rhs.rawValue
}
}
extension TabItem {
var activeTabImage: Image {
switch self {
case .home:
return Image(systemName: "house.fill")
case .notification:
return Image(systemName: "bell.fill")
case .profile:
return Image(systemName: "person.fill")
}
}
var inactiveTabImage: Image {
switch self {
case .home:
return Image(systemName: "house")
case .notification:
return Image(systemName: "bell")
case .profile:
return Image(systemName: "person")
}
}
}
HomeViewの実装
import SwiftUI
struct HomeView: View {
var body: some View {
ScrollView {
VStack {
ForEach(0..<10) { _ in
RoundedRectangle(cornerRadius: 10)
.frame(height: 80)
.foregroundStyle(Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1)))
}
}
.padding(.horizontal, 20)
}
}
}
使用側の実装
import SwiftUI
struct ContentView: View {
@State private var selectedTabItem: TabItem = .home
var body: some View {
TabView(selection: $selectedTabItem) {
HomeView().tag(TabItem.home)
Text("通知").tag(TabItem.notification)
Text("プロフィール").tag(TabItem.profile)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.safeAreaInset(edge: .bottom, spacing: 0) {
CustomTabView(selectedTabItem: $selectedTabItem)
}
}
}
overlayだと、、、
import SwiftUI
struct ContentView: View {
@State private var selectedTabItem: TabItem = .home
var body: some View {
TabView(selection: $selectedTabItem) {
HomeView().tag(TabItem.home)
Text("通知").tag(TabItem.notification)
Text("プロフィール").tag(TabItem.profile)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.overlay(alignment: .bottom) {
CustomTabView(selectedTabItem: $selectedTabItem)
}
}
}
1番下までスクロールすると違いがわかります。
safeAreaInset
の方は重ならないところまでスクロールできます。
しかし、overlay
は1番下までスクロールしてもタブとコンテンツが重なったままです。
safeAreaInset |
overlay |
---|---|
おわり
書いた後に気づいたのですが、これであればVStackでいいですね。
ただ、safeAreaInsetを使うのが正攻法っぽい?かな?
overlayの透過してあるタブもかっこいいので、透過のままタブコンテンツを重ならないところまでスクロールできる方法を考えたいですね。
調査します。
こんな感じでScrollView
にsafeAreaInset
を付与すれば、タブは透過してて、コンテンツは重ならないといった最高の状態になるんですけどね。
import SwiftUI
struct ContentView: View {
@State private var selectedTabItem: TabItem = .home
var body: some View {
ScrollView {
HomeView()
}
.safeAreaInset(edge: .bottom, spacing: 0) {
CustomTabView(selectedTabItem: $selectedTabItem)
}
}
}
Tabの機能を維持したままだと難しいです、、、
公式ドキュメント