I'm using a NavigationSplitView with two columns for a macOS app. The sidebar column shows a list of items and the right pane shows the details for the selected item. I'm using NavigationLink to create clickable sidebar items and pass my own View-type struct that displays the row information.
When I decided to add a slight background color to the rows & slight hover state, I noticed that the row view container is smaller than the NavigationLink when selected (I made the colors brighter to emphasize the issue - rows have a gray background & brighter gray hover state and blue is the built-in styling provided by NavigationLink).
My code for the NavigationSplitView which lives in the body of the main ContentView:
NavigationSplitView(columnVisibility: $columnVisibility) {
VStack{
if userSettings.cases.isEmpty {
Text("Press + to add your first case")
.font(.title2)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
} else {
List(userSettings.cases, selection: self.$selectedCase) { caseData in
NavigationLink(value: caseData) {
CaseRow(caseData: caseData, deleteAction: { caseToDelete in
self.caseToDelete = caseToDelete
})
}
}
}
}
.frame(minWidth: 350, idealWidth: 500)
.sheet(isPresented: $showingAddCaseView) {
AddCaseView { caseName, caseId in
let newCase = Case(id: UUID(), name: caseName, caseId: caseId, caseStatus: "Checking...")
userSettings.cases.append(newCase)
updateCaseStatus(newCase)
showingAddCaseView = false
}
}
.alert(item: $caseToDelete) { caseToDelete in
Alert(title: Text("Delete Case"),
message: Text("Are you sure you want to delete this case?"),
primaryButton: .destructive(Text("Yes, Delete")) {
if let index = userSettings.cases.firstIndex(where: { $0.id == caseToDelete.id }) {
userSettings.cases.remove(at: index)
}
},
secondaryButton: .cancel())
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: {
showingAddCaseView = true
}) {
Image(systemName: "plus")
}
}
}
} detail: {
CaseDetailView(caseData: selectedCase)
.frame(minWidth: 100, maxWidth: .infinity, maxHeight: .infinity)
.background(Color(NSColor.textBackgroundColor))
.navigationTitle(selectedCase?.caseId ?? "No Case Selected")
}
.onAppear() {
columnVisibility = .all
}
}
Code for CaseRow.swift:
import SwiftUI
struct CaseRow: View {
let caseData: Case
let deleteAction: (Case) -> Void
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(caseData.name)
.font(.headline)
Text(caseData.caseId)
.font(.subheadline)
}
Spacer()
Text(caseData.caseStatus)
Button(action: {
deleteAction(caseData)
}) {
Image(systemName: "trash")
.foregroundColor(.red)
}
}
.padding()
.padding(.horizontal, 4)
.textSelection(.enabled)
.rowBackground()
}
}
Code for RowModifier.swift (which applies the background to the row & changes it on hover):
import SwiftUI
struct RowModifier: ViewModifier {
@State private var isHovered = false
func body(content: Content) -> some View {
content
.background(Color.gray.opacity(isHovered ? 0.50 : 0.05))
.cornerRadius(8)
.onHover { hover in
withAnimation(.easeInOut(duration: 0.15)) {
isHovered = hover
}
}
}
}
extension View {
func rowBackground() -> some View {
self.modifier(RowModifier())
}
}
- I tried using .buttonStyle(PlainButtonStyle()) or .buttonStyle(.plain) for the NavigationLink but it doesn't do anything. I did notice that if I apply .buttonStyle(PlainButtonStyle()), the buttons inside the row get new styling, but not the entire row.
- I tried modifying my custom cell styling (RowModifier.swift) with different padding - didn't help either.
- Another sign that something is off with my NavigationLink implementation is that I can't change the accent color from system blue. I tried using .accentColor(.green) and .tint(.green) on the content inside the sidebar and on the entire NavigationSplitView, but neither changed the row selection color - it stays system blue.
- I tried creating a custom NavigationLink and applying the background color & hover color to it directly, but still unable to control the selected state in any way.
I can't find any way to control the system blue styling and its sizing. I just want the row view containers to be the same size in the selected & unselected states so it doesn't look like there's one cell inside another.
I know this could be accomplished by tracking the selected state through bindings, but I'm hoping to use as much of native NavigationSplitView & NavigationLink functionality as it can be ported to iOS later.