SwiftUI NavigationSplitView on macOS: unwanted vertical space in detail column

Hi, colleagues: I've spent days trying to understand this little SwiftUI layout problem, and I've made a minimal test app to explain it. It's based on a Content View of a fixed size.

import SwiftUI

@main
struct TestApp: App {
	
	var body: some Scene {
		
		WindowGroup {
			ContentView()
		}
		.windowResizability(.contentSize)
		.windowToolbarStyle(.unified(showsTitle: false))
		
	}
	
}

struct ContentView: View {
	
	let complaint = """
 What's with the vertical space \
 around me when I'm \
 in a detail column?
 """
	
	var body: some View {
		
		Text(complaint)
			.font(.title)
			.frame(width: 300, height: 300)
			.background(.blue)
		
	}
	
}

And here's the result. As expected, the Content View is hugged nicely by the window:

My goal is to place a fixed-size view like this in the detail column of a Navigation Split View. So I update the scene's root view:

import SwiftUI

@main
struct TestApp: App {
	
	var body: some Scene {
		
		WindowGroup {
			NavigationSplitView(
				sidebar: {

				},
				detail: {
					ContentView()
				}
			)
		}
		.windowResizability(.contentSize)
		.windowToolbarStyle(.unified(showsTitle: false))
		
	}
	
}

struct ContentView: View {
	
	let complaint = """
 What's with the vertical space \
 around me when I'm \
 in a detail column?
 """
	
	var body: some View {
		
		Text(complaint)
			.font(.title)
			.frame(width: 300, height: 300)
			.background(.blue)
		
	}
	
}

And we get the following. This is the window at its minimum size:

I expected the Content View to fit perfectly within the detail column, but as you can see, we have mysterious vertical spaces above and below it. My first guess was that these spaces were some kind of context-dependent default padding. Eventually, I noticed the combined heights of the mysterious spaces equalled the height of the toolbar. I even checked this by using other toolbar styles with different heights – the spaces always adjusted to equal the toolbar's height.

At this point, I was guessing this had something to do with the "safe area" – macOS sometimes treats the toolbar as an area underneath which content can lie, so it might be well-meaningly but redundantly adding the toolbar's height to the detail area or something, and there might be some modifier that will opt out of that behaviour; perhaps ignoresSafeArea(). But nothing I've tried has helped.

I've tried a number of other solutions, but none is ideal, and none help me understand why SwiftUI is adding this vertical space to begin with. Any insight would be very much appreciated.

Replies

A fascinating update. I've tried adding the newly-introduced Inspector modifier, which I also intended to use in the app I'm making:

import SwiftUI

@main
struct TestApp: App {
	
	@State var isShowingInspector: Bool = true

	var body: some Scene {
		
		WindowGroup {
			NavigationSplitView(
				sidebar: {

				},
				detail: {
					ContentView()
				}
			)
			.inspector(isPresented: self.$isShowingInspector) {
				
			}

		}
		.windowResizability(.contentSize)
		.windowToolbarStyle(.unified(showsTitle: false))
		
	}
	
}

struct ContentView: View {
	
	let complaint = """
 What's with the vertical space \
 around me when I'm \
 in a detail column?
 """
	
	var body: some View {
		
		VStack(spacing: 0) {

			Text(complaint)
				.font(.title)
				.ignoresSafeArea()
				.frame(width: 300, height: 300)
				.background(.blue)

		}
		
	}
	
}

This time, SwiftUI apparently adds entire toolbar's height to the minimum height of the window again! Now the detail column's mysterious vertical space equals twice the height of the toolbar:

It doesn't seem to matter whether the Inspector is applied to the Navigation Split View (in order to span the full window height) or to the Content View itself – the detail column's height is the same.

These seem like such simple use cases for such strangeness. Am I missing something incredibly simple…?

Thanks @Starfia, this appears to be a bug. I've filed one on our side. I agree, it looks proportional to some safe area being propagated incorrectly. For now, I can suggest a workaround of

VStack(spacing: 0) {
    Text(complaint)
        .font(.title)
        .frame(minWidth: 300, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
        .background(.blue)
}

But, you may need to subtract the height of the safe areas being added in as the above code results in a blue rect with a size of 352 (i.e. toolbar height + 300)

(rdar://122947424)

Thanks @Starfia, this appears to be a bug.

Thanks very much for confirming that – at least it quiets my indecision about how to proceed. That advice just gives me a Content View that conforms to the detail column's resizable area, though, so I don't think it gives me the fixed-sizedness I'm looking for. The closest I've been able to come has been to hard-code the frame of the Navigation Split View itself, but that involves knowing the combined height of all views I might add to the detail column at all times (alongside which the unwanted safe area propagation persists), so it's a whole undertaking.

I think for this iteration, I just have to avoid using NavigationSplitView or the Inspector; I have to implement alternatives to those provided functionalities, but the layout issues disappear the moment I sidestep them.