SwiftUI: macOS toolbar is initially greyed out - macos

When I first launch my app the toolbar icons are shown and work fine, but they are initially greyed out until mousing over the button.
First run of the app:
and then mouse over:
Here is the code:
import SwiftUI
struct ContentView: View
{
#State var mainList:[String] = ["Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "Indigo", "Juliet", "Kilo", "Lima", "Mike", "November", "Oscar", "Papa", "Quebec", "Romeo", "Sierra", "Tango", "Uniform", "Victor", "Whisky", "Xray", "Yankee", "Zulu"]
#State var selectedItem:String? = "Echo"
var body: some View
{
NavigationView
{
List()
{
ForEach(mainList, id: \.self)
{item in
NavigationLink(destination: DetailView(item: item), tag: item, selection: $selectedItem, label:
{
Text("\(item)")
})
}
}
}
.toolbar
{
Button(action: {addNewItem()})
{
Label("Select", systemImage: "square.and.pencil")
}
}
}
func addNewItem()
{
print ("Add item")
}
}
struct DetailView: View
{
#State var item: String
var body: some View
{
Text(item)
}
}
This is in Xcode 12.5.1 and Xcode 13.0 beta.
I would have thought that the toolbar should be enabled at active with this code. Is it a bug or am I doing something wrong?

The bug seems to be in Button(). Here is a workaround that works for me. The only downside is that it lacks the visual feedback of the Button when pressed.
.toolbar
{
Label("Select", systemImage: "square.and.pencil")
.onTapGesture(perform: {addNewItem()})
}

Related

Toolbar border sometimes has border, sometimes not

I'm making a MacOS app and a list of Mission with its own toolbar, and when clicking on a Mission, some informations about it are displayed with also its own toolbar.
The thing is that sometimes the border is here, sometimes not depending on the size of my Mission list -> here with border and here with no border.
Here is a sample of my code :
Entry point
ZStack {
NavigationLink(
isActive: Binding(
get: { selectedItem == nil },
set: { _ in }),
destination: { NoSelectionView() },
label: { EmptyView() })
List {
ForEach(missionsClients) { missionClient in
NavigationLink(), tag: missionClient.id, selection: $selectedItem, label: { MissionRow() })
}
}
}
.navigationTitle("Missions")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: { createNewMission = true },
label: { Image(systemName: "plus") })
}
}
NoSelectionView
struct NoSelectionView: View {
var body: some View {
Text("No Selection")
.foregroundColor(.gray)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.background()
.ignoresSafeArea()
.toolbar {
ToolbarItem(placement: .destructiveAction) {
Button(action: { }, label: { Image(systemName: "trash") }).disabled(true)
}
ToolbarItem(placement: .primaryAction) {
Button(action: { }, label: { Image(systemName: "square.and.pencil") }).disabled(true)
}
}
}
}
MissionRow
struct MissionRow: View {
var missionClient: MissionClient
var body: some View {
VStack(alignment: .leading) {
if let name = missionClient.mission.name.presence {
Text(name)
} else {
NoNameText()
}
if let clientName = missionClient.client.name.presence {
Text(clientName).foregroundStyle(.secondary)
}
}
}
}

SwiftUI rendering bug with transparent window

In a macOS app, I set NSWindow.backgroundColor = .clear.
When SwiftUI views re-render, their previously rendered versions often get stuck in the background.
Is it a bug, or am I doing something wrong?
Do you know of a workaround?
Environment:
macOS 12.5.1
Xcode 14.0
Thank you in advance.
import SwiftUI
#main
struct MacosPlaygroundApp: App {
var body: some Scene {
WindowGroup("Playground") {
ContentView()
.frame(width: 120, height: 300, alignment: .center)
}
}
}
struct ContentView: View {
private let items: [Item] = [
Item(id: "first", name: "First", sfSymbol: "house"),
Item(id: "second", name: "Second", sfSymbol: "sun.max"),
Item(id: "third", name: "Third", sfSymbol: "cloud"),
Item(id: "fourth", name: "Fourth", sfSymbol: "trash"),
Item(id: "fifth", name: "Fifth", sfSymbol: "scribble"),
]
var body: some View {
ScrollView {
LazyVGrid(columns: [gridItem], alignment: .center, spacing: 10) {
ForEach(items, id: \.id) { item in
itemView(item: item)
}
}
}
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
if let mainWindow = NSApplication.shared.windows.first(where: { $0.title == "Playground" }) {
mainWindow.backgroundColor = .clear
}
}
}
#ViewBuilder
private func itemView(item: Item) -> some View {
VStack(alignment: .center, spacing: 10) {
Image(systemName: item.sfSymbol)
Text(item.name)
}
.padding()
.background(.pink.opacity(0.5))
.cornerRadius(10)
}
private var gridItem: GridItem {
GridItem(
.adaptive(minimum: 100, maximum: 100),
spacing: 10,
alignment: .top
)
}
private struct Item {
let id: String
let name: String
let sfSymbol: String
}
}
I found a workaround:
.backgroundColor = .white.withAlphaComponent(0.000001), instead of .clear.
The window still appears transparent (enough), and I cannot see the rendering bug happen.

Removing the down arrow on a toolbaritem menu

Is there any way to remove the down arrow on a toolbar item when you add a menu to it?
It defaults to:
I tried styling the menu, but it does not affect that button/image.
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu {
Button(action: {}) {
Label("Create a file", systemImage: "doc")
}
Button(action: {}) {
Label("Create a folder", systemImage: "folder")
}
}
label: {
Label("Share", systemImage: "square.and.arrow.up")
}
}
}
Add .menuIndicator(.hidden) to your Menu.

How to enable Gradient Buttons (plus and minus) on macOS SwiftUI

Is there a way to enable Gradient Buttons [ + | – ] with SwiftUI? Can't find any useful information about this topic.
https://developer.apple.com/design/human-interface-guidelines/components/menus-and-actions/buttons#gradient-buttons/
Update
I made a few cosmetic changes to #workingdogsupportUkraine answer. Now Gradient Buttons looks similar as in the Settings app on macOS 13. Please feel free to suggest any enhancements
import SwiftUI
struct TestView: View {
#State private var selection: Int?
struct GradientButton: View {
var glyph: String
var body: some View {
ZStack {
Image(systemName: glyph)
.fontWeight(.medium)
Color.clear
.frame(width: 24, height: 24)
}
}
}
var body: some View {
Form {
Section {
List(selection: $selection) {
ForEach(0 ..< 5) { Text("Item \($0)") }
}
.padding(.bottom, 24)
.overlay(alignment: .bottom, content: {
VStack(alignment: .leading, spacing: 0) {
Divider()
HStack(spacing: 0) {
Button(action: {}) {
GradientButton(glyph: "plus")
}
Divider().frame(height: 16)
Button(action: {}) {
GradientButton(glyph: "minus")
}
.disabled(selection == nil ? true : false)
}
.buttonStyle(.borderless)
}
.background(Rectangle().opacity(0.04))
})
}
}
.formStyle(.grouped)
}
}
you could try something simple like this:
struct ContentView: View {
var body: some View {
List {
ForEach(0 ..< 5) { Text("item \($0)") }
HStack {
Button(action: {print("plus")}) {
Image(systemName: "plus").imageScale(.small)
}
Divider()
Button(action: {print("minus")}) {
Image(systemName: "minus").imageScale(.small)
}
}.buttonStyle(.borderless)
.frame(width: 66, height: 33)
}
}
}

SwiftUI macOS toolbar icons alignment for three column layout

Following this question, how to add a toolbar divider for a three column view in swiftUI life cycle
, I have a slightly different issue. I am trying to achive the same thing but the second and third columns are contained in a view which in turn is added inside a NavigationView next to first column which is the Sidebar.
Code example
import SwiftUI
#main
struct ThreeColumnsAppApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.frame(minWidth: 900, maxWidth: .infinity, minHeight: 600, maxHeight: .infinity)
}
.windowStyle(DefaultWindowStyle())
.windowToolbarStyle(UnifiedWindowToolbarStyle(showsTitle: true))
}
}
struct ContentView: View {
var body: some View {
NavigationView {
Sidebar()
.toolbar { Button(action: {}, label: { Image(systemName: "sidebar.left") }) }
MainContentView()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct Sidebar: View {
var body: some View {
List {
Text("Menu 1")
Text("Menu 2")
Text("Menu 3")
Text("Menu 4")
}
.frame(minWidth: 250)
.listStyle(SidebarListStyle())
}
}
struct MainContentView: View {
var body: some View {
NavigationView {
ListItemView()
DetailView()
}
.navigationTitle("Items List")
.navigationSubtitle("5 items found")
}
}
struct ListItemView: View {
var body: some View {
List {
Text("List item 1")
Text("List item 2")
Text("List item 3")
Text("List item 4")
Text("List item 5")
}
.frame(minWidth: 250)
.listStyle(InsetListStyle())
.toolbar {
Button(action: {}, label: { Image(systemName: "arrow.up.arrow.down.square") })
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail of list 1")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.toolbar {
Button(action: {}, label: { Image(systemName: "plus") })
Button(action: {}, label: { Image(systemName: "minus") })
Button(action: {}, label: { Image(systemName: "pencil") })
}
}
}
Output
As you can see bellow, the arrow icon, which should be on the second column's toolbar, is pushed to the right together with the detail view's toolbar icons. It seems NavigationView sees ListItemView and DetailView toolbars as a single one.
Desired output
Question
So, my question is how to have the toolbar icons aligned with their view?
In order to do this properly, you need a ToolbarItem on each of the three columns of the view. For example, this:
struct TripleNavView: View {
var body: some View {
NavigationView {
Text("Column 1")
.toolbar {
ToolbarItem {
Button(action: {}, label: {Image(systemName: "sidebar.left")})
}
}
Text("Column 2")
.toolbar {
ToolbarItem {
Button(action: {}, label: {Image(systemName: "plus")})
}
}
Text("Column 3")
.toolbar {
ToolbarItem {
Button(action: {}, label: {Image(systemName: "trash")})
}
}
}
.navigationTitle("Title")
}
}
Produces this:
The important thing is that both columns 2 and 3 need to have a ToolbarItem of some kind. If you only put a button on one of the columns, then SwiftUI will place it all the way at the trailing edge of the toolbar, and ruin the look.
As a result, if you don't want a button on a particular column, substitute a Spacer in place of the button.
Note that leaving it blank or using EmptyView() won't work - SwiftUI will optimize it out, and act as if you didn't include a ToolbarItem in the first place!

Resources