Skip to content

Instantly share code, notes, and snippets.

@grantjbutler
Created December 2, 2023 15:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grantjbutler/4ff23c054e36e7c09ca6b608b7c43fb4 to your computer and use it in GitHub Desktop.
Save grantjbutler/4ff23c054e36e7c09ca6b608b7c43fb4 to your computer and use it in GitHub Desktop.
A TabView-like implementation for getting a "tabs in the toolbar" style of tab bar on macOS.
import SwiftUI
struct ToolbarTabView<Content: View>: View {
let content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
var body: some View {
_VariadicView.Tree(_ToolbarTabView_Layout(), content: content)
}
}
private struct _ToolbarTabView: NSViewControllerRepresentable {
let children: _VariadicView.Children
func makeNSViewController(context: Context) -> NSTabViewController {
let viewController = NSTabViewController()
viewController.tabStyle = .toolbar
return viewController
}
func updateNSViewController(_ nsViewController: NSTabViewController, context: Context) {
context.coordinator.updateChildren(children, inViewController: nsViewController)
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
final class Coordinator {
var children: [AnyHashable: NSTabViewItem] = [:]
func updateChildren(_ children: _VariadicView.Children, inViewController viewController: NSTabViewController) {
for (key, tabViewItem) in self.children {
if children.contains(where: { $0.id == key }) { continue }
self.children.removeValue(forKey: key)
viewController.removeTabViewItem(tabViewItem)
}
for (index, child) in zip(children.indices, children) {
let toolbarTabItem = child[ToolbarTabItem.self]
if let tabViewItem = self.children[child.id] {
viewController.removeTabViewItem(tabViewItem)
updateTabViewItem(tabViewItem, with: toolbarTabItem)
viewController.insertTabViewItem(tabViewItem, at: index)
} else {
let tabViewItem = makeTabViewItem(for: child)
updateTabViewItem(tabViewItem, with: toolbarTabItem)
viewController.insertTabViewItem(tabViewItem, at: index)
}
}
}
private func updateTabViewItem(_ tabViewItem: NSTabViewItem, with toolbarTabItem: ToolbarTabItem?) {
tabViewItem.label = toolbarTabItem?.title ?? UUID().uuidString
tabViewItem.image = toolbarTabItem?.image
}
private func makeTabViewItem(for child: _VariadicView.Children.Element) -> NSTabViewItem {
let tabViewItem = NSTabViewItem()
tabViewItem.viewController = NSHostingController(rootView: child)
self.children[child.id] = tabViewItem
return tabViewItem
}
}
}
private struct _ToolbarTabView_Layout: _VariadicView_ViewRoot {
func body(children: _VariadicView.Children) -> some View {
_ToolbarTabView(children: children)
}
}
// MARK: -
private struct ToolbarTabItem: Equatable {
let title: String
let image: NSImage
}
extension ToolbarTabItem: _ViewTraitKey {
static var defaultValue: ToolbarTabItem?
}
extension View {
func toolbarTabItem(title: String, image: NSImage) -> some View {
_trait(ToolbarTabItem.self, .init(title: title, image: image))
}
func toolbarTabItem(title: String, systemSymbolName: String) -> some View {
self.toolbarTabItem(title: title, image: NSImage(systemSymbolName: systemSymbolName, accessibilityDescription: nil)!)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment