A library that provides boilerplate for an Xcode-like macOS app UI.
Some behaviour and code extracted from AuroraEditor.
- Customisable navigation sidebar with pages
- Customisable inspector sidebar with pages
- Tabular main content
- Tab Bar
- Custmisable using
TabBarIDandTabBarRepresentableprotocols - Drag to rearrange
- Resize/scroll on tab excess
- Animation on close/open/scroll
- Custmisable using
- Customisable toolbar
- Easy-to-use OutlineView template
- Settings Page
- About Page
- Window and Sidebar behaviour:
MainWindowController: A Window Controller that creates the basic UINavigatorProtocol: A protocol which defines the behaviour of the Navigator sidebar (on the left)InspectorProtocol: A protocol which defines the behaviour of the Inspector sidebar (on the right)WorkspaceProtocol: A protocol which defines the behaviour of the main tab content (in the center)
- Outline Views:
OutlineViewController: An NSViewController that controls an NSOutlineView in an NSScrollViewOutlineElement: A protocol for an item inOutlineViewController, for use inStandardTableViewCellStandardTableViewCell: A class for a row inOutlineViewController. Intended to be subclassed.
- Tab Bar:
TabBarID: A protocol that includes an ID. Intended to be implemented on an Enum, so that each tab has an assignedcase.TabBarRepresentable: A protocol that provides information like the title and icon to show for a tab. Intended to be implemented on a class or struct.
See the example repo for reference
- Create a class conforming to
NavigatorProtocolorInspectorProtocol. Add new pages by creating newSidebarDockIconinstances innavigatorItemsorinspectorItemslike so:
.init(imageName: "symbolName", title: "hoverTitle", id: 0)- Override the
viewForNavigationSidebar(selection: Int) -> AnyVieworviewForInspectorSidebar(selection: Int) -> AnyViewfunctions. UseMainContentWrapperto wrap your View so that it is formatted properly, eg:
func viewForNavigationSidebar(selection: Int) -> AnyView {
MainContentWrapper {
switch selection {
case 0:
// things to show on page 0
Text("PAGE ZERO")
// add more cases as needed
default:
Text("Not Implemented Yet")
}
}
}Add an extra case to the selection switch statement (the number you use is the id
of your SidebarDockIcon). Put your view there, and it will be visible when that tab is
selected.
- Override the default
NavigatorProtocolin yourMainWindowControllersubclass by overriding thefunc getNavigatorProtocol() -> any NavigatorProtocolorfunc getInspectorProtocol() -> any InspectorProtocolfunction, eg:
override func getNavigatorProtocol() -> any NavigatorProtocol {
return MyNavigatorProtocol()
}- Create an enum conforming to
TabBarItemID. You should ONLY MAKE ONE in the entire project, and add cases for other tabs - Add a case to your enum. This case can hold information (eg. by using
case myCase(String)to hold a string). - To store data for the tab type, create a class conforming to
TabBarItemRepresentable. See thetestcase and theTestElementclass as an example in the example repo
- To open a new tab, use the
openTabfunction of theTabManagerEnvironmentObject, available in all SwiftUI subviews of the sidebars and workspace view. It is also accessible via theMainWindowControllersubclass instance astabManager. - Similarly, use the
closeTabfunction to close your tab.
- Create a class conforming to
WorkspaceProtocol - Implement the
viewForTab(tab: TabBarItemID) -> AnyViewfunction. You can use the following as a base:
func viewForTab(tab: TabBarItemID) -> AnyView {
MainContentWrapper {
if let tab = tab as? MyTabBarItemID {
switch tab {
case .myCase(let string):
// things to show on page 0
Text("My Case: \(string)")
// add more cases as needed
default:
Text("Not Implemented Yet")
}
}
}
}- Override the default
WorkspaceProtocolin yourMainViewControllersubclass by overriding thefuncc getWorkspaceProtocol() -> any WorkspaceProtocolfunction, eg:
override func getWorkspaceProtocol() -> any WorkspaceProtocol {
return MyWorkspaceProtocol()
}- Create a subclass of
OutlineElementto hold the information in each view in the OutlineView - Create a subclass of the
StandardTableViewCell - Create a subclass of
OutlineViewController. You NEED to override the following functions:
loadContent(), which returns an array of anOutlineElementoutlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any), which returns an NSView for a given element. This will be your custom instance ofStandardTableViewCell.
- Use
OutlineViewif you plan to use your custom outline view within SwiftUI.
See TestElement, TestOutlineViewController, and TestTableViewCell
for an example in the example repo
Override the toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier],
toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier], and
open func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? functions in your MainWindowController subclass.
Extend NSToolbarItem.Identifier to add custom identifiers. See the apple developer documentation for more details
Remember that you can use defaultLeadingItems() and defaultTrailingItems to get the default leading and trailing items, which are the toolbar items
for the sidebar show/hide buttons. You can also use the builtinDefaultToolbar( _ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool ) -> NSToolbarItem? function to get the NSToolbarItem for a given identifier, or nil if it is not a toolbar item that MainWindowController implements.