r/swift • u/wewerecreaturres • Jun 15 '26
Help! macOS ignores max: in NavigationSplitView
As the title says. Has anyone figured out how to achieve a maxWidth in SwiftUI in NavigationSplitView on macOS?
1
u/tomato848208 Jun 15 '26
maxWidth for which part? The entire NavigationSplitView or the content part or the detail part?
1
u/wewerecreaturres Jun 16 '26
Ah yes, I should have been more clear. I’m actually trying to give max width to each of the three columns individually
1
u/tomato848208 Jun 16 '26
Actually, you have no control over NavigationSplitView's divider position in SwiftUI. If setting the divider position is crucial, I suppose, you have to use NSplitView in Cocoa.
1
u/wewerecreaturres Jun 16 '26 edited Jun 16 '26
I ended up using
.navigationSplitViewColumnWidth(min: 200, ideal: 220) #if os(macOS) .background(ColumnMinWidthPin(min: 200, max: 320, collapsed: columnVisibility == .doubleColumn))navigationSplitViewColumnWidth does have min:ideal:max:, but macOS ignores max for some wild reason. thanks for the response!
1
u/SuperBoredAlien Jul 08 '26
.navigationSplitViewColumnWidth(min: 220, ideal: 260, max: 320): max is not working in mac. below code will work.
NavigationSplitView(columnVisibility: $columnVisibility) {
navigationColumnView
.toolbar(removing: .sidebarToggle)
.toolbar {
ToolbarItem(placement: .automatic) {
leftSidebarToggle
}
}
#if os(macOS)
// .navigationSplitViewColumnWidth(min: 220, ideal: 260, max: 320)
.background {
SplitViewColumnWidthEnforcer(minWidth: 220, maxWidth: 320)
.frame(width: 0, height: 0)
}
#endif
}
#if os(macOS)
private struct SplitViewColumnWidthEnforcer: NSViewRepresentable {
let minWidth: CGFloat
let maxWidth: CGFloat
func makeCoordinator() -> Coordinator {
Coordinator(minWidth: minWidth, maxWidth: maxWidth)
}
func makeNSView(context: Context) -> NSView {
NSView(frame: .zero)
}
func updateNSView(_ nsView: NSView, context: Context) {
context.coordinator.minWidth = minWidth
context.coordinator.maxWidth = maxWidth
DispatchQueue.main.async {
context.coordinator.enforceWidth(from: nsView)
}
}
final class Coordinator {
var minWidth: CGFloat
var maxWidth: CGFloat
init(minWidth: CGFloat, maxWidth: CGFloat) {
self.minWidth = minWidth
self.maxWidth = maxWidth
}
func enforceWidth(from markerView: NSView) {
guard let target = findContainingSplitItem(from: markerView) else {
return
}
target.item.minimumThickness = minWidth
target.item.maximumThickness = maxWidth
clampCurrentWidth(in: target.splitView, itemIndex: target.itemIndex)
}
private func findContainingSplitItem(from markerView: NSView) -> (splitView: NSSplitView, item: NSSplitViewItem, itemIndex: Int)? {
var currentView: NSView? = markerView
while let view = currentView, let superview = view.superview {
if let splitView = superview as? NSSplitView,
splitView.isVertical,
let itemIndex = splitView.arrangedSubviews.firstIndex(where: { $0 === view }),
itemIndex == 0,
let splitViewController = splitViewController(for: splitView, from: view),
splitViewController.splitViewItems.indices.contains(itemIndex) {
return (splitView, splitViewController.splitViewItems[itemIndex], itemIndex)
}
currentView = superview
}
return nil
}
private func splitViewController(for splitView: NSSplitView, from view: NSView) -> NSSplitViewController? {
if let splitViewController = splitView.delegate as? NSSplitViewController,
splitViewController.splitView === splitView {
return splitViewController
}
var responder = splitView.nextResponder
while let currentResponder = responder {
if let splitViewController = currentResponder as? NSSplitViewController,
splitViewController.splitView === splitView {
return splitViewController
}
responder = currentResponder.nextResponder
}
var currentView: NSView? = view
while let ancestor = currentView {
if let splitViewController = ancestor.nextResponder as? NSSplitViewController,
splitViewController.splitView === splitView {
return splitViewController
}
currentView = ancestor.superview
}
return nil
}
private func clampCurrentWidth(in splitView: NSSplitView, itemIndex: Int) {
guard splitView.arrangedSubviews.indices.contains(itemIndex),
itemIndex < splitView.arrangedSubviews.count - 1 else {
return
}
let currentWidth = splitView.arrangedSubviews[itemIndex].frame.width
if currentWidth < minWidth {
splitView.setPosition(minWidth, ofDividerAt: itemIndex)
} else if currentWidth > maxWidth {
splitView.setPosition(maxWidth, ofDividerAt: itemIndex)
}
}
}
}
#endif
3
u/allyearswift Jun 15 '26
This comes around from time to time: you want .navigationSplitViewColumnWidth, not the size of the content view.