I am designing a SwiftUI wrapper for NSTabViewController with the toolbar style. I want it to be a drop-in replacement for TabView. TabView uses a modifier tabItem(_:)
to specify the tab name and icon. So I designed a similar modifier for my own ToolbarTabView
:
extension View {
func toolbarTabItem(_ label: LocalizedStringKey, nsImage: NSImage? = nil, tooltip: LocalizedStringKey? = nil) -> some View {
self.preference(key: ToolbarTabItemPreferenceKey.self, value: ToolbarTabItemPreference(label: label, nsImage: nsImage, tooltip: tooltip))
}
}
I wrap each View
in a NSHostingController
and create a NSTabViewItem
. Then I use onPreferenceChange
to set the NSTabViewItem
's label
and image
property. Finally, I have a NSViewControllerRepresentable
to pass my array of NSTabViewItem
to a NSTabViewController
. This all works well except for the following issue.
By design NSTabViewController
will only load its first tab. This loads the first NSHostingController
which lays out the first View
. That calls onPreferenceChange
and sets the label
for the first tab. However, the remaining tabs are not loaded and therefore the label
remains unset.
I know that I can re-design my APIs to pass in the labels and images explicitly and that works, but then how does Apple implement their TabView
? They must have the same issue with the views being lazy loaded because the macOS implementation of TabView
looks like NSTabViewController
.
I think a workaround would be to force all the tabs to load, which is the title of this question, but I am open to other ideas as well.
Reference:
https://github.com/utmapp/UTM/blob/dev/Platform/macOS/ToolbarTabView.swift
https://github.com/utmapp/UTM/blob/dev/Platform/macOS/ToolbarTabViewController.swift
Here is the dumb workaround I came up with
Basically I force load every tab once the view appears. I really hope there's a better answer than this but I'll leave it here just in case.