I have a three column layout macOS application with the first being the sidebar. I have a button that toggles that enabling the user to hide the sidebar.
On Xcode and other macOS, the toggle sidebar button resides on the toolbar on top of the sidebar, and becomes part of the main toolbar when the sidebar is hidden.
For example, open sidebar on Xcode:
And when you hide the sidebar:
I have added the toolbar with the toggle sidebar to the view containing my sidebar, and another toolbar to the second column, but still toggle sidebar appears on the main toolbar, on top of the second column.
Am I missing anything? Here's the code:
// sidebar view, first of three columns
struct ContentView: View {
#State var selection: Set<Int> = [0]
var body: some View {
NavigationView {
List(selection: self.$selection) {
NavigationLink(destination: AllData()) {
Label("All Data", systemImage: "note.text")
}
.tag(0)
Label("Trash", systemImage: "trash")
}
.listStyle(SidebarListStyle())
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: toggleSidebar, label: {
Image(systemName: "sidebar.left") }).help("Toggle Sidebar")
}
}
}
}
func toggleSidebar() {
NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
}
// second column view, with the rest of the toolbar
struct AllData: View {
var body: some View {
NavigationView {
List(filteredData) {
// list items here.
}
.toolbar {
ToolbarItem(placement: .automatic) {
Button("Press Me") {
print("Pressed")
}
}
ToolbarItem(placement: .automatic) {
Button("Press Me too") {
print("Pressed too")
}
}
}
}
}
}
If you use the .frame modifier on the sidebar then you are able to set constraints for minimum width, ideal width and maximum width.
I find a minWidth: 148 ensures the sidebar cannot be collapsed to the point where SwiftUI shuffles the toolbar button off to the trailing edge of the nav toolbar and behind (need to click) the double chevron expander.
So your code might look like this...
...
var body: some View {
NavigationView {
List(selection: self.$selection) {
NavigationLink(destination: AllData()) {
Label("All Data", systemImage: "note.text")
}
.tag(0)
Label("Trash", systemImage: "trash")
}
.listStyle(.sidebar) //<-- from iOS 14 and macOS 10.15
.frame(minWidth: 148, idealWidth: 160, maxWidth: 192, maxHeight: .infinity)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: {
toggleSidebar
}, label: {
Image(systemName: "sidebar.left")
})
.help("Toggle Sidebar")
}
}
}
}
...
PS. I note that the .primaryAction and .status modifiers both place the sidebar button in a similar position, although I've not tested this thoroughly or completed any research.
try this:
ToolbarItem(placement: .primaryAction) {
Related
When I toggle the sidebar in my macOS app from the toolbar, sometimes the share button on the upper right jitters (see video). This does not happen on every click, so it might take a few tries to reproduce with the example. The app I am working on has much more content, and there it happens more frequently.
I assume this has something to do with using two .toolbar modifiers: When I move the sidebar button to the content area and trigger from there, it seems like the jittering is gone.
Any idea how to fix this? I was not able to recreate this toolbar button layout without using two .toolbar modifiers. Code below video.
struct ContentView: View {
var body: some View {
NavigationView {
Text("Sidebar")
.frame(width: 300)
.toolbar {
sidebarButton
}
Text("Content")
.frame(minWidth: 500)
.toolbar {
shareButton
}
}
.frame(height: 500)
}
var sidebarButton: some View {
Button {
NSApp.keyWindow?.firstResponder?.tryToPerform(
#selector(NSSplitViewController.toggleSidebar(_:)), with: nil
)
} label: {
Image(systemName: "sidebar.left")
}
}
var shareButton: some View {
Button {
print("Share")
} label: {
Image(systemName: "square.and.arrow.up")
}
}
}
I want to increase the icon button of the Menu in SwiftUI for a macOS app, but modifiers such as .imageScale(.large), .resizable(), .scaleEffect(1.2), and changing font doesn't work.
image
Menu {
Button("Quit") {}
} label: {
Image(systemName: "gear")
.font(.title)
.resizable()
.scaleEffect(1.2)
.imageScale(.large)
}
.menuStyle(.borderlessButton)
.menuIndicator(.hidden)
How can I change icon size?
Use ImageinText.
struct DetailView: View {
var body: some View {
Text("hello")
.contextMenu {
Button(action: { }) {
Text(Image(systemName: "gear"))
.font(.largeTitle)
}
Button(action: { }) {
Image(systemName: "gear")
}
}
}
}
I'm working on a macOS app, with the view layers written in SwiftUI. I know that iOS toolbars can have the background color changed at least, but when I try to do this in macOS, it doesn't behave as I'd expect.
Here's a (simplified) example:
struct ContentView: View {
var body: some View {
NavigationView {
Collections()
.layoutPriority(0)
Photos()
.frame(maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
.background(Color.Alt.black)
.layoutPriority(1)
}
.toolbar {
Toolbar().background(Color.red500)
}
}
}
struct Toolbar: View {
var body: some View {
Group {
Slider(value: 250, in: 150...400) {
Text("Toolbar.PreviewSize")
} minimumValueLabel: {
Image(systemName: "photo").resizable().scaledToFit().frame(width: 15)
} maximumValueLabel: {
Image(systemName: "photo").resizable().scaledToFit().frame(width: 23)
} onEditingChanged: { _ in
// do nothing
}.frame(minWidth: 200)
Spacer()
Text("Toolbar.SelectionCount")
Spacer()
AddPhotosButton()
}
}
}
Which produces something like this, which as you can see, doesn't apply the background color to the entire toolbar, just to the items in the toolbar:
I'm guessing I could make my own WindowToolbarStyle style, but there's no documentation on the protocol!
If I make my own toolbar as a View rather than with the .toolbar modifier, I can't read the safe area insets for the window traffic buttons when the sidebar is collapsed, resulting in a complete mess:
Thanks for any help!
I recommend
{
.toolbar {
Toolbar()
}.toolbarBackground(Color.gray)
}
I am looking for a way to achieve a Toolbar for a three column layout like Mail.app. Also the Notes.app uses almost the same Toolbar with the only important difference between the two apps being that Notes.app looks like it's WindowStyle is a HiddenTitleBarWindowStyle and Mail.app looks like a Default|TitleBarWindowStyle.
Following should be true:
If the sidebar is collapsed there is a list and detail view
The dividing line that separates the list from the detail view goes all the way up through the toolbar. (This could be achieved with a HiddenTitleBarWindowStyle)
If the Title is too long to fit into the navigation list, the vertical dividing line will be broken: The list is still divided from the Detail View as before, but now the Toolbar looks like a DefaultWindowStyle with only a small Divider()-like line in the Toolbar.
Which combination of WindowStyle, WindowToolbarStyle and .toolbar configuration do I need to achieve this setup?
EDIT
I noticed that it is not possible to remove the divider line that Notes.app shows. I have not found a references to any such element in the docs though.
Code Example
I have distilled the problem down to a simple app with mostly toolbar content. In my original code I use two nested NavigationViews, while for the example I used only one NavigationView with two Lists. However the Toolbar results are the same.
Toolbar with DefaultWindowStyle
import SwiftUI
#main
struct ToolbarTestApp: App {
var body: some Scene {
WindowGroup {
ContentView(titleBarIsHidden: true)
}
.windowToolbarStyle(UnifiedWindowToolbarStyle())
.commands {
SidebarCommands()
}
}
}
struct ContentView: View {
#State var destination: String = "Toolbar Test"
#State var detail: String = ""
var body: some View {
NavigationView {
List {
Button(action: {self.destination = "Item with the identifier: 1"}, label: {
Text("Item 1")
})
.buttonStyle(DefaultButtonStyle())
Button(action: {self.destination = "Item 2"}, label: {
Text("Item 2")
})
.buttonStyle(DefaultButtonStyle())
}
.listStyle(SidebarListStyle())
List {
NavigationLink(
destination: DetailView(text: "\(destination) – \(detail)").onAppear{self.detail = "Detail 1"},
label: {
Text("\(destination) – Detail 1")
})
NavigationLink(
destination: DetailView(text: "\(destination) – \(detail)").onAppear{self.detail = "Detail 2"},
label: {
Text("\(destination) – Detail 2")
})
}
.listStyle(InsetListStyle())
Text("\(destination) – \(detail)")
}
.navigationTitle(destination)
.navigationSubtitle(detail)
.toolbar(id: "nav") {
ToolbarItem(id: "plus", placement: ToolbarItemPlacement.principal, showsByDefault: true) {
HStack {
Button(action: {print("pressed")}, label: {
Image(systemName: "plus.circle")
})
}
}
ToolbarItem(id: "spacer", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
HStack {
Spacer()
}
}
ToolbarItem(id: "sidebar.end", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
Button(action: {print("pressed")}, label: {
Image(systemName: "sidebar.right")
})
}
}
}
}
This example will result in a Toolbar that will never show the divider line that splits the whole Toolbar in two parts. Also the first ToolbarItem is positioned in the center of the Toolbar. I tried all ToolbarItemPlacement but none caused the item to move to the far left adjacent to the title.
Toolbar with HiddenTitleBarWindowStyle
#main
struct ToolbarTestApp: App {
var body: some Scene {
WindowGroup {
ContentViewForHiddenTitleBar()
}
.windowStyle(HiddenTitleBarWindowStyle()) // added hidden title style
.windowToolbarStyle(UnifiedWindowToolbarStyle())
.commands {
SidebarCommands()
}
}
}
struct ContentViewForHiddenTitleBar: View {
#State var destination: String = "Toolbar Test"
#State var detail: String = ""
var body: some View {
NavigationView {
List {
Button(action: {self.destination = "Item with the identifier: 1"}, label: {
Text("Item 1")
})
.buttonStyle(DefaultButtonStyle())
Button(action: {self.destination = "Item 2"}, label: {
Text("Item 2")
})
.buttonStyle(DefaultButtonStyle())
}
.listStyle(SidebarListStyle())
// add geometry reader to trim title width in toolbar
GeometryReader { geometry in
List {
NavigationLink(
destination: DetailView(text: "\(destination) – \(detail)").onAppear{self.detail = "Detail 1"},
label: {
Text("\(destination) – Detail 1")
})
NavigationLink(
destination: DetailView(text: "\(destination) – \(detail)").onAppear{self.detail = "Detail 2"},
label: {
Text("\(destination) – Detail 2")
})
}
// there is no title anymore so let's fake it.
.toolbar(id: "list navigation") {
ToolbarItem(id: "title", placement: ToolbarItemPlacement.navigation, showsByDefault: true) {
VStack(alignment: .leading) {
Text(destination)
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
Text(detail)
.font(.subheadline)
.opacity(0.6)
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(width: geometry.size.width)
}
}
}
.listStyle(InsetListStyle())
Text("\(destination) – \(detail)")
}
.navigationTitle(destination)
.navigationSubtitle(detail)
.toolbar(id: "nav") {
// primary action will place the item next to the divider line.
ToolbarItem(id: "plus", placement: ToolbarItemPlacement.primaryAction, showsByDefault: true) {
HStack {
Button(action: {print("pressed")}, label: {
Image(systemName: "plus.circle")
})
}
}
ToolbarItem(id: "spacer", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
HStack {
Spacer()
}
}
ToolbarItem(id: "sidebar.end", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
Button(action: {print("pressed")}, label: {
Image(systemName: "sidebar.right")
})
}
}
}
}
This example will result in a Toolbar that will always show a full height divider. Even if the title is too long. Therefore a GeometryReader was added. This is fine until the sidebar will collapse. The placement of ToolbarItems will not be correct. Also when customising the Toolbar there would be the possibility to remove the title, which should not be possible.
The default title bar style is fine, you just need to attach your toolbar items to the subviews of your top NavigationView, e.g.:
var body: some View {
NavigationView {
List {
...
}
.listStyle(SidebarListStyle())
.toolbar {
ToolbarItem {
Button(action: { }, label: {
Image(systemName: "sidebar.right")
})
}
}
List {
...
}
.listStyle(InsetListStyle())
.toolbar {
ToolbarItem {
Button(action: { }, label: {
Image(systemName: "plus.circle")
})
}
}
Text("\(destination) – \(detail)")
}
.navigationTitle(destination)
.navigationSubtitle(detail)
}
I didn’t attach any toolbar items to the third column (the Text) but you can — just be sure to attach the same toolbar items to your DetailViews, as its toolbar will replace the Text’s toolbar when the user navigates. (if you’re not sure what I mean, just try it and you’ll quickly see what I’m talking about :)
I do have an app with a sidebar where I place my main navigation. When I navigate from the sidebar my DetailView is shown next to the sidebar as it should. But when I want to navigate from my MainView to a DetailView it's not working. The view is only shown as an overlay.
Is there any way I can navigate around my main window from the sidebar and from the main view itself?
import SwiftUI
#main
struct NavigationTestApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
MainView()
}
}
}
}
struct ContentView: View {
var body: some View {
List {
NavigationLink(
destination: DetailView(),
label: {
Text("DetailView")
})
}
.listStyle(SidebarListStyle())
}
}
struct MainView: View {
var body: some View {
NavigationLink(
destination: DetailView(),
label: {
Text("Navigate") /* Navigation does not work properly here*/
})
.frame(width: 1280, height: 720)
}
}
struct DetailView: View {
var body: some View {
Text("DetailView")
.frame(width: 1280, height: 720)
}
}