Created
December 2, 2023 15:36
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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