SwiftUIのTabViewでタブ移動をキャンセルするのは少し工夫が必要です。selectedTabを一旦戻す方法で実装します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
struct ContentView: View { private enum Tabs: Hashable { case OCR, PDF, image, setting, screenshot, unlock } @Environment(SettingDataManager.self) private var SDM @Environment(UnlockManager.self) private var UM @State private var selectedTab: Tabs = .OCR @State private var editing_flg: Bool = false @State private var showMoveAlert: Bool = false @State private var pendingTab: Tabs? = nil // 移動先を一時保存 @State private var previousTab: Tabs = .screenshot // 移動前のタブを保存 var body: some View { TabView(selection: $selectedTab) { if UM.is_premium { ImportTextView() .tabItem { Label("< 範囲 >", systemImage: "viewfinder.circle") } .tag(Tabs.OCR) PDFconversion() .tabItem { Label("< PDF >", systemImage: "doc.fill") } .tag(Tabs.PDF) ImageConversion() .tabItem { Label("< 画像 >", systemImage: "photo") } .tag(Tabs.image) CustomScreenshot() .tabItem { Label("< ペイント >", systemImage: "paintbrush") } .tag(Tabs.screenshot) SettingView() .tabItem { Label("< 設定 >", systemImage: "gearshape") } .tag(Tabs.setting) } UnlockView() .tabItem { Label("< アンロック >", systemImage: "lock.open") } .tag(Tabs.unlock) } .onChange(of: selectedTab) { oldValue, newValue in // screenshotタブからの移動時のみチェック guard oldValue == .screenshot else { return } if SDM.editing_image_flg { // タブを即座に元に戻す selectedTab = oldValue // 移動先と移動前を保存してアラート表示 pendingTab = newValue previousTab = oldValue showMoveAlert = true } } .alert("確認", isPresented: $showMoveAlert) { Button("はい") { // 移動先タブへ遷移 if let tab = pendingTab { selectedTab = tab } pendingTab = nil } Button("いいえ", role: .cancel) { // 何もしない(すでに元のタブに戻っている) pendingTab = nil } } message: { Text("移動しますか?") } } } |
ポイント解説
なぜ selectedTab = oldValue を先に戻すのか
SwiftUIのTabViewはタブ選択を内部状態で管理するため、onChangeの中で単純にアラートを出すだけでは見た目上タブが移動してしまいます。そのため、アラートを出す前に即座に元のタブへ戻し、ユーザーが「はい」を選んだ場合のみ pendingTab に保存した移動先へ遷移させます。
フロー整理
|
1 2 3 4 5 6 7 8 9 10 11 |
タブ変更検知 ↓ editing_image_flg が true? ↓ Yes selectedTab を oldValue に即戻す ↓ pendingTab に newValue を保存 ↓ アラート表示 ├── 「はい」→ selectedTab = pendingTab で移動 └── 「いいえ」→ 何もしない(既に元タブ) |
.onChange()以外で何か方法はありますか?(タブが変わる処理をする前にこの確認を行いたい場合)
残念ながら、SwiftUIのTabViewには 「タブ変更を事前にインターセプトする」公式なAPIは存在しません。
現実的な代替手段をいくつか紹介します。
① タブアイテムをカスタムボタンで自作する
TabViewを使わず、タブバー自体を自前で実装する方法です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
struct ContentView: View { @State private var selectedTab: Tabs = .screenshot @State private var showMoveAlert = false @State private var pendingTab: Tabs? = nil var body: some View { VStack(spacing: 0) { // メインコンテンツ Group { switch selectedTab { case .screenshot: CustomScreenshot() case .OCR: ImportTextView() // ...他のタブ } } .frame(maxWidth: .infinity, maxHeight: .infinity) Divider() // カスタムタブバー HStack { ForEach(Tabs.allCases, id: \.self) { tab in Button { trySelectTab(tab) // ← ここで事前チェック } label: { Label(tab.title, systemImage: tab.icon) } .buttonStyle(.plain) } } .padding() } .alert("確認", isPresented: $showMoveAlert) { Button("はい") { selectedTab = pendingTab! } Button("いいえ", role: .cancel) { } } message: { Text("移動しますか?") } } private func trySelectTab(_ tab: Tabs) { guard selectedTab == .screenshot, SDM.editing_image_flg else { selectedTab = tab // 条件なければそのまま移動 return } pendingTab = tab showMoveAlert = true // アラートを出してから判断 } } |
タブ変更の前に処理を挟めるので、今回の目的に最も素直に合います。ただしタブバーのデザインを自分で作る手間があります。
② @Bindable + カスタムBinding で selectedTab を監視
Bindingのsetをラップして変更を横取りする方法です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var interceptedTab: Binding<Tabs> { Binding( get: { selectedTab }, set: { newTab in if selectedTab == .screenshot && SDM.editing_image_flg { pendingTab = newTab showMoveAlert = true // set しないので TabView は動かない } else { selectedTab = newTab } } ) } // 使い方 TabView(selection: interceptedTab) { ... } |
onChangeより意図が明確ですが、macOSのTabViewでは動作が不安定なケースがあるため注意が必要です。
結論
| 方法 | 事前インターセプト | 安定性 | 手間 |
|---|---|---|---|
onChange で即戻す(現行) | 疑似的に可能 | ◎ | 少 |
| カスタムタブバー | ✅ 真の意味で可能 | ◎ | 多 |
| カスタムBinding | ほぼ可能 | △ | 中 |
「タブが変わる前に確認したい」という目的であれば、カスタムタブバーが唯一の完全な解決策です。 現行の onChange 方式で実用上問題なければ、そのままが一番シンプルです。
コメント