I am trying to come up with a way to prevent a TabView from changing tabs. I started with this but it didn't seem to work.
What I have coded is
import SwiftUI
import Combine
import Foundation
import PlaygroundSupport
enum TabIndexes
{
case Tab1
case Tab2
case Tab3
case Tab4
}
/*
* Protocol for class that stores tab index
*/
protocol TabSelectorStorage: AnyObject
{
associatedtype TabSelectionType
var tabSelection: TabSelectionType { get set }
/*
* Returns true if tab change to "newTabIndex" is allowed
*/
func tabChangeAllowed(_ newTabSelection: TabSelectionType) -> Bool
}
/*
* Property wrapper for tab selection variable in view
*/
@propertyWrapper struct TabSelector<T, U> where T: Hashable, U: TabSelectorStorage, U.TabSelectionType == T
{
private var storage: U
init(tabStorage: U)
{
self.storage = tabStorage
}
var wrappedValue: T
{
get { storage.tabSelection }
set { assignNewSelection(newValue) }
}
var projectedValue: Binding<T>
{
.init
{ wrappedValue }
set:
{ newValue in assignNewSelection(newValue) }
}
/*
* Can't assigned to wrappedValue in projectedValue but we
* can assign to tabClass.tabIndex so this makes the code common
*/
func assignNewSelection(_ newSelection : T)
{
if storage.tabChangeAllowed(newSelection)
{
storage.tabSelection = newSelection
}
print(storage.tabSelection)
}
}
class TabIndex: ObservableObject, TabSelectorStorage
{
/*
* TabIndexStorage protocol
*/
typealias TabSelectionType = TabIndexes
@Published var tabSelection: TabSelectionType
/*
*
*/
init(defaultSelection: TabSelectionType)
{
self.tabSelection = defaultSelection
}
/*
* Tab management
*/
func tabChangeAllowed(_ newSelection: TabSelectionType) -> Bool
{
//print(tabSelection, newSelection)
if newSelection != tabSelection
{
switch newSelection
{
case .Tab1:
return true
case .Tab2:
return true
case .Tab3:
return false
case .Tab4:
return false
}
}
return true
}
}
/*
*
*/
struct MainView: View
{
@TabSelector<TabIndexes, TabIndex> var tabIndex: TabIndexes
init(storage: TabIndex)
{
_tabIndex = TabSelector(tabStorage: storage)
}
var body: some View
{
TabView(selection: $tabIndex)
{
Text("Tab 1")
.tabItem
{
Image(systemName: "1.circle")
}
.tag(TabIndexes.Tab1)
Text("Tab 2")
.tabItem
{
Image(systemName: "2.circle")
}
.tag(TabIndexes.Tab2)
Text("Tab 3")
.tabItem
{
Image(systemName: "3.circle")
}
.tag(TabIndexes.Tab3)
Text("Tab 4")
.tabItem
{
Image(systemName: "4.circle")
}
.tag(TabIndexes.Tab4)
}
}
}
let storage = TabIndex(defaultSelection: TabIndexes.Tab1)
let view = MainView(storage: storage)
let hostingVC = UIHostingController(rootView: view)
PlaygroundPage.current.liveView = hostingVC
The idea is to use a custom property wrapper to keep the tab selection from changing when I don't want it to (for data validation). The property wrapper calls the TabStorage class' tabChangeAllowed() with the new tab index. If it's allowed, it should return true, false otherwise. What happens is that the tab view happily changes to all tabs. Even though the tabChangeAllowed() function returns false for tabs 3 and 4. The print statement prints "Tab2" when clicking Tab3 or Tab4 so the property wrapper appears to be working. I don't understand how the tab selection storage can be one value and the TabView be on another value.
Ideally, I would want the TabStorage class to be a value in the MainView struct, but that led to the dreaded "self use before all variables initialized" error.
I'm using Xcode 13.4.