In the following sample code, hope its not to long, I have difficulties to get it right. At first I did want to use a enum with associated values and actions, but choose a way that I did understand a little better.
In my current code, the .tag's are giving me a little headache, I did try several methods to get it right but to no a veal. The errors it shows is "No exact matches in reference to static method 'buildExpression'" on the line '.tag(item.tag)'and '.tabItem' line.
import SwiftUI
class SettingsTabItemManager: ObservableObject {
@Published var tabItems: [SettingsTabItem] = []
init() {
createTabItems()
}
func createTabItems() {
self.tabItems = [
SettingsTabItem(
label: "General",
labelImage: "gear",
tag: "general",
action: {},
view: GeneralSettingsView(),
description: "The general settings tabItem"),
SettingsTabItem(
label: "Network",
labelImage: "network",
tag: "network",
action: {},
view: NetworkSettingsView(),
description: "The network settings tabItem"),
SettingsTabItem(
label: "Advanced",
labelImage: "star",
tag: "advanced",
action: {},
view: AdvancedSettingsView(),
description: "The advanced settings tabItem")
]
}
}
struct SettingsTabItem {
var id : UUID = UUID()
var label: String
var labelImage: String
var tag: String // or enum Tabs
var action: () -> Void
var view: any View
var description: String
}
struct SettingsView: View {
@StateObject private var tabItemManager = SettingsTabItemManager()
@State private var selectedTab: String?
init() {
_selectedTab = State(initialValue: tabItemManager.tabItems.first?.tag)
}
@State var tapped = false
var body: some View {
TabView {
Group {
ForEach(tabItemManager.tabItems, id: \.id) { item in
item.view
.tabItem {
Label(item.label, systemImage: item.labelImage)
}
.tag(item.tag) // I did try also ( .tag(item.tag.hashValue) )
}
.onTapGesture {
tapped.toggle()
// more is coming
}
}
.padding(20)
.frame(width: 375, height: 150)
}
}
}
struct GeneralSettingsView: View {
var body: some View {
Text("GeneralSettingsView")
}
}
struct NetworkSettingsView: View {
var body: some View {
Text("NetworkSettingsView")
}
}
struct AdvancedSettingsView: View {
var body: some View {
Text("AdvancedSettingsView")
}
}
What do I overlook here? Maybe I should do it different but this is what I came up with. Maybe I should use some sort of viewbuilder, but don't know that how to. Thanks in advance.
You should not have a
Viewin yourSettingsTabItem.I assume you want to be able to change the tabs in the tab view dynamically, and that's why you created the
SettingsTabItemstruct. So the array ofSettingsTabItems represents state, and views in SwiftUI are a function of state.You should write a function that, given a
SettingsTabItem, return someView. For example, in the simple case you showed here:You can expand this function to do whatever things you need to do, to figure out what the
Viewcorresponding to a givenSettingsTabItemshould be. You might need to add more properties toSettingsTabItemin your real scenario.Notice that I avoided the problem of the views all having different types by using
@ViewBuilder, and this could only be done if the view is written as a function of state. You tried to useany Viewto solve this problem, and that caused the error.Then you can just call this function in
ForEach:Side note: The way you initialise
selectedTabis an an anti pattern. (See also the many answers here) I would suggest initialisingselectedTabto some dummy value first, then assign it the first tab's tag inonAppear, and/or every time the tab items do not contain an item with the tagselectedTab(you can detect this withonChange).