5

I have an AppKit app where I'd like to embed a SwiftUI view using NSHostingView. The SwiftUI view is based on a VStack, and looks like this:

struct IPReportView: View {
//This is rather long, but contains some lazy grid views that result in an overall view
//with a fixed width and height.
}

struct IPReportDetailView: View {
    @Binding var report: IPReportDocument?
    
    var body: some View {
        if let selectedReport = report {
            VStack(spacing: 20.0) {
                Text(selectedReport.title)
                    .font(.headline)
                IPReportView(reportDocument: selectedReport)
                Spacer()
            }
        } else {
            IPEmptyMessageView(header: "No report selected")
        }
    }
}

//Eventually there'll be a Split View but I just need to get the basics working first
struct IPReportListSplitView: View {
    @ObservedObject var reportListViewModel: IPReportListViewModel
    @State var selectedReport: IPReportDocument?
    
    var body: some View {
        IPReportDetailView(report: $selectedReport)
    }
}

The setup for the hosting view looks like so:

//BWViewController is an NSViewController subclass. The calling code inserts
//IPReportListViewController's view with constraints to pin its sides to all four
//sides of its superview
class IPReportListViewController: BWViewController {
    lazy var reportListViewModel: IPReportListViewModel = IPReportListViewModel(reports: IPReportDocument.allStoredReports())
    override func loadView() {
        let reportListMainView = IPReportListSplitView(reportListViewModel: self.reportListViewModel, selectedReport: self.reportListViewModel.reports.first)
        let hostingView = NSHostingView(rootView: reportListMainView)
        hostingView.translatesAutoresizingMaskIntoConstraints = false
        hostingView.layer?.borderWidth = 1.0
        hostingView.layer?.borderColor = NSColor.green.cgColor
        self.view = hostingView
    }
    
}

When I run this, the window I get looks like this. Note the green border that denotes the bounds of the NSHostingView. What I'm trying to achieve is have the NSHostingView take up the entire bounds of its superview, and have the report view aligned to the top of the NSHostingView. Screenshot

What I would like to find is some way to have the IPReportDetailView declare that it wants as much height as the NSHostingView will offer it, rather than having the NSHostingView shrink its height to match the intrinsic content size of the IPReportDetailView. The normal stack to have a stack eat up additional space is to add a Spacer(), which I've done here, but in the context of a hosting view that doesn't seem to do anything. I've also tried appending .frame(maxHeight: .infinity) in various places, but to no effect.

I know the NSHostingView will take up the whole content area in other cases, e.g. if I set the report binding to nil and it shows IPEmptyMessageView, which is basically a Color(.clear) with a text overlay that does take up the whole content area. Ultimately the hosted SwiftUI View will be a HSplitView, with a SwiftUI List of reports on the left and the report detail on the right, so I don't want to "solve" the problem by adding space with AppKit stuff around the NSHostingView. Any ideas are appreciated.

1
  • I was able to make the NSHostingView resize with its parent by setting the max width and height for the SwiftUI view to infinity, using .frame(maxWidth: .infinity, maxHeight: .infinity). Commented Oct 10, 2024 at 10:47

1 Answer 1

3

Alright, by the end of the day I was able to figure it out. If you're targeting macOS 13 or later, you can set the sizingOptions property of your NSHostingView to [.minSize], which will only look at the SwiftUI sizing to determine the minimum size, but not intrinsic or maximum size.

However, I'm targeting macOS 12, but I was able to find an equivalent solution, which is to subclass NSHostingView and override the intrinsicContentSize property like so:

    override var intrinsicContentSize: NSSize {
        var size = super.intrinsicContentSize
        size.height = NSView.noIntrinsicMetric
        return size
    }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.