I'm not sure you should try to do this, for a couple of reasons. Look at some simple code that's written for iOS 17 and 18:
if #available(iOS 18, *) {
TabView(selection: $selectedTab) {
Tab("Watch Now", systemImage: "play", value: .watchNow) {
WatchNowView()
}
}
} else {
TabView(selection: $selectedTab) {
MenuView()
.tabItem {
Label("Menu", systemImage: "list.dash")
}
OrderView()
.tabItem {
Label("Order", systemImage: "square.and.pencil")
}
}
}
First reason: The iOS 18 format has a specific Tab object which has its own title and image in the Tab item itself, whereas iOS 17's tabItems force you to create a Label instead. That would be difficult to work around.
Second reason: Tab contains a View, whereas tabItem is a modifier on a View (just as .bold() is a modifier on a Text view). It's the opposite way round, making it difficult again.
It might be cleaner to simply do something like this, and you can remove the unnecessary code when you no longer support iOS 17:
if #available(iOS 18, *) {
MyTabs(selectedTab: $selectedTab)
} else {
MyTabs_iOS17(selectedTab: $selectedTab)
}
...
struct MyTabs: View {
@Binding var selectedTab: Tabs
var body: some View {
if #available(iOS 18, *) {
TabView(selection: $selectedTab) {
Tab("Watch Now", systemImage: "play", value: .watchNow) {
WatchNowView()
}
}
}
}
}
struct MyTabs_iOS17: View {
@Binding var selectedTab: Tabs
var body: some View {
TabView(selection: $selectedTab) {
MenuView()
.tabItem {
Label("Menu", systemImage: "list.dash")
}
OrderView()
.tabItem {
Label("Order", systemImage: "square.and.pencil")
}
}
}
}
There are ways to do this, but as I've mentioned, you'll be trying to fit A into B and C into D, and will have to write a chunk of code that makes that happen. It will likely be easier to bite the bullet and write the two separate sections.