From 2eff868d23101f1e47ed61f2560af2856f44f743 Mon Sep 17 00:00:00 2001 From: Mx-Iris Date: Sun, 8 Feb 2026 21:13:04 +0800 Subject: [PATCH 1/2] Update AppKit Components & Examples --- Example/HostingExample/AppDelegate.swift | 26 - Example/HostingExample/ViewController.swift | 24 +- .../App/App/AppKit/AppKitAppDelegate.swift | 13 +- .../Controller/NSHostingController.swift | 62 ++- .../Hosting/AppKit/View/NSHostingView.swift | 454 +++++++++++++++++- 5 files changed, 514 insertions(+), 65 deletions(-) diff --git a/Example/HostingExample/AppDelegate.swift b/Example/HostingExample/AppDelegate.swift index 7a4d85a03..f59bcb480 100644 --- a/Example/HostingExample/AppDelegate.swift +++ b/Example/HostingExample/AppDelegate.swift @@ -63,30 +63,4 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } } -final class WindowController: NSWindowController { - init() { - super.init(window: nil) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override var windowNibName: NSNib.Name? { "" } - - lazy var viewController = ViewController() - - override func loadWindow() { - window = NSWindow(contentRect: .init(x: 0, y: 0, width: 500, height: 300), styleMask: [.titled, .closable, .miniaturizable], backing: .buffered, defer: false) - window?.center() - } - - override func windowDidLoad() { - super.windowDidLoad() - - contentViewController = viewController - } -} - #endif diff --git a/Example/HostingExample/ViewController.swift b/Example/HostingExample/ViewController.swift index e83e46bc3..c903f4085 100644 --- a/Example/HostingExample/ViewController.swift +++ b/Example/HostingExample/ViewController.swift @@ -17,8 +17,6 @@ import UIKit import AppKit #endif -import OpenSwiftUI - #if os(iOS) || os(visionOS) class ViewController: UINavigationController { override func viewDidAppear(_ animated: Bool) { @@ -51,21 +49,21 @@ final class EntryViewController: UIViewController { } } #elseif os(macOS) -class ViewController: NSViewController { - override func loadView() { - view = NSHostingView(rootView: ContentView()) +final class WindowController: NSWindowController { + init() { + super.init(window: nil) } - override func viewDidLoad() { - super.viewDidLoad() - - view.frame = .init(x: 0, y: 0, width: 500, height: 300) + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - override var representedObject: Any? { - didSet { - // Update the view, if already loaded. - } + override var windowNibName: NSNib.Name? { "" } + + override func loadWindow() { + window = NSWindow(contentViewController: NSHostingController(rootView: ContentView().frame(width: 500, height: 300))) + window?.center() } } #endif diff --git a/Sources/OpenSwiftUI/App/App/AppKit/AppKitAppDelegate.swift b/Sources/OpenSwiftUI/App/App/AppKit/AppKitAppDelegate.swift index d398f072c..b8e05db2d 100644 --- a/Sources/OpenSwiftUI/App/App/AppKit/AppKitAppDelegate.swift +++ b/Sources/OpenSwiftUI/App/App/AppKit/AppKitAppDelegate.swift @@ -59,10 +59,13 @@ class AppDelegate: NSResponder, NSApplicationDelegate { // FIXME let items = AppGraph.shared?.rootSceneList?.items ?? [] let view = items[0].value.view - let hostingVC = NSHostingController(rootView: view) + let hostingVC = NSHostingController(rootView: view.frame(width: 500, height: 300)) let windowVC = WindowController(hostingVC) windowVC.showWindow(nil) + self.windowVC = windowVC } + + var windowVC: NSWindowController? } // FIXME: frame is zero @@ -82,15 +85,9 @@ final class WindowController: NSWindowController where Content: View { let hostingVC: NSHostingController override func loadWindow() { - window = NSWindow(contentRect: .init(x: 0, y: 0, width: 500, height: 300), styleMask: [.titled, .closable, .miniaturizable], backing: .buffered, defer: false) + window = NSWindow(contentViewController: hostingVC) window?.center() } - - override func windowDidLoad() { - super.windowDidLoad() - contentViewController = hostingVC - hostingVC.host.frame = window!.frame - } } // MARK: - App Utils diff --git a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/Controller/NSHostingController.swift b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/Controller/NSHostingController.swift index b3c860fae..1d58beac7 100644 --- a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/Controller/NSHostingController.swift +++ b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/Controller/NSHostingController.swift @@ -54,6 +54,7 @@ open class NSHostingController: NSViewController where Content: View { func _commonInit() { host.viewController = self + self.view = host } /// Creates a hosting controller object from the contents of the specified @@ -68,11 +69,9 @@ open class NSHostingController: NSViewController where Content: View { public required init?(coder: NSCoder) { preconditionFailure("init(coder:) must be implemented in a subclass and call super.init(coder:, rootView:)") } - - open override func loadView() { - view = host - } - + + /// The root view of the SwiftUI view hierarchy managed by this view + /// controller. public var rootView: Content { get { host.rootView } set { host.rootView = newValue } @@ -127,6 +126,55 @@ open class NSHostingController: NSViewController where Content: View { set { host.safeAreaRegions = newValue } } + /// The options for which aspects of the window will be managed by this + /// controller's hosting view. + /// + /// `NSHostingController` will populate certain aspects of its associated + /// window, depending on which options are specified. + /// + /// For example, a hosting controller can manage its window's toolbar by + /// including the `.toolbars` option: + /// + /// struct RootView: View { + /// var body: some View { + /// ContentView() + /// .toolbar { + /// MyToolbarContent() + /// } + /// } + /// } + /// + /// let controller = NSHostingController(rootView: RootView()) + /// controller.sceneBridgingOptions = [.toolbars] + /// + /// When this hosting controller is set as the `contentViewController` for a + /// window, the default value for this property will be `.all`, which + /// includes the options for `.toolbars` and `.title`. Otherwise, the + /// default value is `[]`. + public var sceneBridgingOptions: NSHostingSceneBridgingOptions { + get { host.sceneBridgingOptions } + set { host.sceneBridgingOptions = newValue } + } + + open override var preferredContentSize: NSSize { + get { + if sizingOptions.contains(.preferredContentSize) { + return host.idealSize() + } else { + return super.preferredContentSize + } + } + set { + super.preferredContentSize = newValue + } + } + + open override var identifier: NSUserInterfaceItemIdentifier? { + didSet { + host.identifier = identifier + } + } + /// Calculates and returns the most appropriate size for the current view. /// /// - Parameter size: The proposed new size for the view. @@ -140,6 +188,10 @@ open class NSHostingController: NSViewController where Content: View { return result } + public func _render(seconds: Double) { + host.render(interval: seconds, targetTimestamp: nil) + } + public func _forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) { host.forEachIdentifiedView(body: body) } diff --git a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift index f3cbe027c..e6fc7e527 100644 --- a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift +++ b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift @@ -78,6 +78,37 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont } } + /// The options for which aspects of the window will be managed by this + /// hosting view. + /// + /// `NSHostingView` will populate certain aspects of its associated + /// window, depending on which options are specified. + /// + /// For example, a hosting view can manage its window's toolbar by including + /// the `.toolbars` option: + /// + /// struct RootView: View { + /// var body: some View { + /// ContentView() + /// .toolbar { + /// MyToolbarContent() + /// } + /// } + /// } + /// + /// let view = NSHostingView(rootView: RootView()) + /// view.sceneBridgingOptions = [.toolbars] + /// + /// When this hosting view is set as the `contentView` for a window, the + /// default value for this property will be `.all`, which includes the + /// options for `.toolbars` and `.title`. Otherwise, the default value is + /// `[]`. + public var sceneBridgingOptions: NSHostingSceneBridgingOptions = [] { + didSet { + // TODO + } + } + private var _rootView: Content package final let viewGraph: ViewGraph @@ -96,6 +127,8 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont } } + private var cachedIntrinsicContentSize = CGSize(width: NSView.noIntrinsicMetric, height: NSView.noIntrinsicMetric) + private var displayLink: DisplayLink? package var externalUpdateCount: Int = .zero @@ -117,13 +150,66 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont return externalUpdateCount > 0 } - open override var isFlipped: Bool { true } - + final override public var isFlipped: Bool { true } + + open override class var requiresConstraintBasedLayout: Bool { true } + open override var layerContentsRedrawPolicy: NSView.LayerContentsRedrawPolicy { set { } get { .duringViewResize } } - + + open override var intrinsicContentSize: NSSize { + guard sizingOptions.contains(.intrinsicContentSize), + !checkForReentrantLayout() + else { + return cachedIntrinsicContentSize + } + + var size = idealSize() + let pixelLength = convertFromBacking(CGSize(width: 1, height: 1)) + size.width = size.width.rounded(.up, toMultipleOf: pixelLength.width) + size.height = size.height.rounded(.up, toMultipleOf: pixelLength.height) + + if size.width == 0 || size.height == 0 { + let minSize = self.minSize() + let maxSize = self.maxSize() + if maxSize.width >= 2777777.0 && minSize.width == 0 && size.width == 0 { + size.width = NSView.noIntrinsicMetric + } + if maxSize.height >= 2777777.0 && minSize.height == 0 && size.height == 0 { + size.height = NSView.noIntrinsicMetric + } + } + + cachedIntrinsicContentSize = size + return cachedIntrinsicContentSize + } + + private func _layoutSizeThatFits(_ size: CGSize, fixedAxes: UInt) -> CGSize { + guard sizingOptions.contains(.intrinsicContentSize), + !checkForReentrantLayout() + else { + return cachedIntrinsicContentSize + } + + let maxValue = 2777777.0 + let fittingSize = sizeThatFits(.init( + width: size.width >= maxValue ? nil : size.width, + height: size.height >= maxValue ? nil : size.height + )) + + let pixelLength = convertFromBacking(CGSize(width: 1, height: 1)) + let result = fittingSize.rounded(.up, toMultipleOf: pixelLength.width) + + cachedIntrinsicContentSize = result + return result + } + + private var _axesForDerivingIntrinsicContentSizeFromLayoutSize: UInt { + sizingOptions.contains(.intrinsicContentSize) ? 3 : 0 + } + /// Creates a hosting view object that wraps the specified SwiftUI view. /// /// - Parameter rootView: The root view of the SwiftUI view hierarchy that @@ -147,6 +233,8 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont HostingViewRegistry.shared.add(self) // TODO Update.end() + // TODO + setNeedsUpdate() } /// Creates a hosting view object from the contents of the specified @@ -187,12 +275,142 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont private var isUpdating = false + private var isUpdatingConstraints = false + + private var isCreatingEnvironment = false + + private static var _presentationTime: Double { + if let value = CATransaction.value(forKey: "NSHostingViewPresentationTime") as? Double { + return value + } + let time = CACurrentMediaTime() + CATransaction.setValue(NSNumber(value: time), forKey: "NSHostingViewPresentationTime") + return time + } + + @objc + func _willUpdateConstraintsForSubtree() { + isUpdatingConstraints = true + defer { isUpdatingConstraints = false } + + guard needsUpdateConstraints, + !checkForReentrantLayout() else { + return + } + + Update.locked { + cancelAsyncRendering() + } + + if canAdvanceTimeAutomatically { + let time = Time(seconds: Self._presentationTime) + advanceTime(with: time) + } + + if translatesAutoresizingMaskIntoConstraints { + sizeConstraints?.deactivate() + sizeConstraints = nil + } else { + if sizeConstraints == nil { + sizeConstraints = SizeConstraints() + } + sizeConstraints?.update(from: self) + } + } + + open override func updateConstraints() { + _willUpdateConstraintsForSubtree() + updateWindowContentSizeExtremaIfNecessary() + // TODO: notify delegate (hostingViewDidUpdateConstraints) + super.updateConstraints() + } + + open override func setFrameSize(_ newSize: NSSize) { + let oldSize = frame.size + super.setFrameSize(newSize) + if oldSize != newSize { + invalidateProperties([.size, .containerSize], mayDeferUpdate: false) + } + } + + open override func viewWillMove(toWindow newWindow: NSWindow?) { + let oldWindow = self.window + let center = NotificationCenter.default + + if let oldWindow { + center.removeObserver(self, name: NSWindow.didBecomeMainNotification, object: oldWindow) + center.removeObserver(self, name: NSWindow.didResignMainNotification, object: oldWindow) + center.removeObserver(self, name: NSWindow.didBecomeKeyNotification, object: oldWindow) + center.removeObserver(self, name: NSWindow.didResignKeyNotification, object: oldWindow) + // TODO: NSWindowDidOrderOnScreenNotification / NSWindowDidOrderOffScreenNotification (private API) + center.removeObserver(self, name: NSWindow.willBeginSheetNotification, object: oldWindow) + center.removeObserver(self, name: NSWindow.didEndSheetNotification, object: oldWindow) + center.removeObserver(self, name: NSWindow.didChangeScreenNotification, object: oldWindow) + // TODO: removeObserver for KVO on "showsWindowSharingTitlebarButton" + removeWindowContentSizeExtremaIfNecessary() + } + + if let newWindow { + center.addObserver(self, selector: #selector(windowDidChangeMain), name: NSWindow.didBecomeMainNotification, object: newWindow) + center.addObserver(self, selector: #selector(windowDidChangeMain), name: NSWindow.didResignMainNotification, object: newWindow) + center.addObserver(self, selector: #selector(windowDidChangeKey), name: NSWindow.didBecomeKeyNotification, object: newWindow) + center.addObserver(self, selector: #selector(windowDidChangeKey), name: NSWindow.didResignKeyNotification, object: newWindow) + // TODO: NSWindowDidOrderOnScreenNotification / NSWindowDidOrderOffScreenNotification → windowDidChangeVisibility + center.addObserver(self, selector: #selector(windowWillBeginSheet), name: NSWindow.willBeginSheetNotification, object: newWindow) + center.addObserver(self, selector: #selector(windowDidEndSheet), name: NSWindow.didEndSheetNotification, object: newWindow) + center.addObserver(self, selector: #selector(windowDidChangeScreen), name: NSWindow.didChangeScreenNotification, object: newWindow) + // TODO: FirstResponderObserver KVO setup + // TODO: KVO on "showsWindowSharingTitlebarButton" + } + invalidateProperties(.environment) + super.viewWillMove(toWindow: newWindow) + } + + open override func viewDidChangeBackingProperties() { + // TODO + invalidateProperties(.environment) + needsUpdateConstraints = true + invalidateIntrinsicContentSize() + } + + open override func viewDidChangeEffectiveAppearance() { + if !isCreatingEnvironment { + invalidateProperties(.environment) + } + } + + open override func viewDidMoveToWindow() { + Update.begin() + // TODO: delegate handling + if window != nil { + invalidateProperties(.transform) + } else { + Update.locked { + cancelAsyncRendering() + } + } + updateRemovedState() + // TODO: initialInheritedEnvironment / inferredGraphTraits + super.viewDidMoveToWindow() + Update.end() + } + + open override func prepareForReuse() { + // TODO + } + + open override func didChangeValue(forKey key: String) { + super.didChangeValue(forKey: key) + if key == "safeAreaInsets" { + invalidateSafeAreaInsets() + } + } + open override func layout() { - super.layout() guard canAdvanceTimeAutomatically else { return } - guard !isPerformingLayout else { + guard !checkForReentrantLayout() else { needsDeferredUpdate = true return } @@ -264,6 +482,87 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont private var isInsertingRenderedSubview: Bool = false + private var sizeConstraints: SizeConstraints? + + private struct SizeConstraints { + var minSizeConstraints: (width: NSLayoutConstraint, height: NSLayoutConstraint)? + var maxSizeConstraints: (width: NSLayoutConstraint, height: NSLayoutConstraint)? + var idealHeightConstraint: NSLayoutConstraint? + var huggingIdealHeightConstraint: NSLayoutConstraint? + + mutating func deactivate() { + if let min = minSizeConstraints { + min.width.isActive = false + min.height.isActive = false + } + if let max = maxSizeConstraints { + max.width.isActive = false + max.height.isActive = false + } + idealHeightConstraint?.isActive = false + huggingIdealHeightConstraint?.isActive = false + } + + mutating func update(from hostingView: NSHostingView) { + let options = hostingView.sizingOptions + + if options.contains(.minSize) { + let size = hostingView.minSize() + if let existing = minSizeConstraints { + existing.width.constant = size.width + existing.width.isActive = true + existing.height.constant = size.height + existing.height.isActive = true + } else { + let w = hostingView.widthAnchor.constraint(greaterThanOrEqualToConstant: size.width) + w.identifier = "NSHostingView.minWidth" + w.priority = .init(999) + w.isActive = true + let h = hostingView.heightAnchor.constraint(greaterThanOrEqualToConstant: size.height) + h.identifier = "NSHostingView.minHeight" + h.priority = .init(999) + h.isActive = true + minSizeConstraints = (w, h) + } + } else if let existing = minSizeConstraints { + existing.width.isActive = false + existing.height.isActive = false + minSizeConstraints = nil + } + + if options.contains(.maxSize) { + let size = hostingView.maxSize() + let widthFinite = size.width < 2777777.0 && !size.width.isInfinite + let heightFinite = size.height < 2777777.0 && !size.height.isInfinite + if let existing = maxSizeConstraints { + if widthFinite { + existing.width.constant = size.width + } + existing.width.isActive = widthFinite + if heightFinite { + existing.height.constant = size.height + } + existing.height.isActive = heightFinite + } else { + let w = hostingView.widthAnchor.constraint(lessThanOrEqualToConstant: widthFinite ? size.width : 0) + w.identifier = "NSHostingView.maxWidth" + w.priority = .init(999) + w.isActive = widthFinite + let h = hostingView.heightAnchor.constraint(lessThanOrEqualToConstant: heightFinite ? size.height : 0) + h.identifier = "NSHostingView.maxHeight" + h.priority = .init(999) + h.isActive = heightFinite + maxSizeConstraints = (w, h) + } + } else if let existing = maxSizeConstraints { + existing.width.isActive = false + existing.height.isActive = false + maxSizeConstraints = nil + } + // TODO + } + } + weak var viewController: NSHostingController? var colorScheme: ColorScheme? = nil { @@ -272,6 +571,96 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont public final func _viewDebugData() -> [_ViewDebug.Data] { [] } + private func checkForReentrantLayout() -> Bool { + if isPerformingLayout { + Log.externalWarning("NSHostingView is being laid out reentrantly") + return true + } + return false + } + + private func updateWindowContentSizeExtremaIfNecessary() { + guard let window, window.contentView === self else { return } + + var contentMinWidth: CGFloat = 0 + var contentMinHeight: CGFloat = 0 + if sizingOptions.contains(.minSize) { + let size = minSize() + let pixelLength = convertFromBacking(CGSize(width: 1, height: 1)) + contentMinWidth = size.width.rounded(.up, toMultipleOf: pixelLength.width) + contentMinHeight = size.height.rounded(.up, toMultipleOf: pixelLength.height) + } + + var contentMaxWidth: CGFloat = .greatestFiniteMagnitude + var contentMaxHeight: CGFloat = .greatestFiniteMagnitude + if sizingOptions.contains(.maxSize) { + let size = maxSize() + let pixelLength = convertFromBacking(CGSize(width: 1, height: 1)) + contentMaxWidth = size.width.rounded(.up, toMultipleOf: pixelLength.width) + contentMaxHeight = size.height.rounded(.up, toMultipleOf: pixelLength.height) + } + + contentMaxWidth = Swift.max(contentMaxWidth, contentMinWidth) + contentMaxHeight = Swift.max(contentMaxHeight, contentMinHeight) + + let currentMin = window.contentMinSize + let currentMax = window.contentMaxSize + + var changed = false + if contentMinWidth != currentMin.width || contentMinHeight != currentMin.height { + window.contentMinSize = CGSize(width: contentMinWidth, height: contentMinHeight) + changed = true + } + if contentMaxWidth != currentMax.width || contentMaxHeight != currentMax.height { + window.contentMaxSize = CGSize(width: contentMaxWidth, height: contentMaxHeight) + changed = true + } + + if changed { + let contentRect = window.contentRect(forFrameRect: window.frame) + var width = contentRect.width + var height = contentRect.height + + width = Swift.min(width, window.contentMaxSize.width) + width = Swift.max(width, window.contentMinSize.width) + height = Swift.min(height, window.contentMaxSize.height) + height = Swift.max(height, window.contentMinSize.height) + + if width != contentRect.width || height != contentRect.height { + window.setContentSize(CGSize(width: width, height: height)) + } + } + } + + private func removeWindowContentSizeExtremaIfNecessary() { + guard let window, window.contentView === self else { return } + window.contentMinSize = .zero + window.contentMaxSize = NSSize( + width: CGFloat.greatestFiniteMagnitude, + height: CGFloat.greatestFiniteMagnitude + ) + } + + private func invalidateSafeAreaInsets() { + invalidateProperties(.safeArea) + } + + private func minSize() -> CGSize { + sizeForProposal(.zero) + } + + private func maxSize() -> CGSize { + sizeForProposal(.infinity) + } + + private func sizeForProposal(_ proposal: _ProposedSize) -> CGSize { + var size = sizeThatFits(proposal) + let pixelLength = convertFromBacking(CGSize(width: 1, height: 1)) + size.width = size.width.rounded(.up, toMultipleOf: pixelLength.width) + size.height = size.height.rounded(.up, toMultipleOf: pixelLength.height) + return size + } + func clearUpdateTimer() { guard Thread.isMainThread else { return @@ -286,11 +675,6 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont DisplayLinkSetNextTime(displayLink, .zero) return true } - guard let window, - let screen = window.screen - else { - return false - } // TODO: screen.displayID let displayID = CGMainDisplayID() @@ -402,6 +786,39 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont needsLayout = true } + // MARK: - Window Notification Handlers + + @objc + func windowDidChangeMain() { + // TODO: update main window state + } + + @objc + func windowDidChangeKey() { + // TODO: update key window state + invalidateProperties(.environment) + } + + @objc + func windowDidChangeVisibility() { + // TODO: update window visibility state + } + + @objc + func windowWillBeginSheet() { + // TODO: handle sheet presentation + } + + @objc + func windowDidEndSheet() { + // TODO: handle sheet dismissal + } + + @objc + func windowDidChangeScreen() { + // TODO: handle screen change + } + @objc(swiftui_addRenderedSubview:positioned:relativeTo:) // FIXME: ViewUpdater -> AppKitAddSubview private func openswiftui_addRenderedSubview(_ view: NSView, positioned place: NSWindow.OrderingMode, relativeTo otherView: NSView?) { isInsertingRenderedSubview = true @@ -481,7 +898,18 @@ extension NSHostingView { @available(visionOS, unavailable) extension NSHostingView: ViewRendererHost { package func updateEnvironment() { - // TODO + var environment: EnvironmentValues + if let inheritedEnvironment { + environment = inheritedEnvironment + } else if let initialInheritedEnvironment { + environment = initialInheritedEnvironment + } else { + environment = EnvironmentValues() + } + if let environmentOverride { + environment.plist.override(with: environmentOverride.plist) + } + viewGraph.setEnvironment(environment) } package func updateSize() { @@ -493,7 +921,7 @@ extension NSHostingView: ViewRendererHost { } package func updateContainerSize() { - // TODO + viewGraph.setContainerSize(ViewSize.fixed(bounds.size)) } package func updateRootView() { @@ -554,7 +982,7 @@ extension NSHostingView: HostingViewProtocol { } extension NSHostingView/*: TestHost*/ { - func forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) { + final package func forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) { let tree = preferenceValue(_IdentifiedViewsKey.self) let adjustment = { [weak self](rect: inout CGRect) in guard let self else { return } From c3e21c97cf67eb0bdb185a32f2545846c3ffa90c Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 8 Feb 2026 22:15:07 +0800 Subject: [PATCH 2/2] Update documentation --- .../Controller/NSHostingController.swift | 26 +++++++++---------- .../Hosting/AppKit/View/NSHostingView.swift | 26 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/Controller/NSHostingController.swift b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/Controller/NSHostingController.swift index 1d58beac7..4a6ffcb98 100644 --- a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/Controller/NSHostingController.swift +++ b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/Controller/NSHostingController.swift @@ -9,10 +9,10 @@ public import AppKit -/// An AppKit view controller that hosts SwiftUI view hierarchy. +/// An AppKit view controller that hosts OpenSwiftUI view hierarchy. /// -/// Create an `NSHostingController` object when you want to integrate SwiftUI -/// views into an AppKit view hierarchy. At creation time, specify the SwiftUI +/// Create an `NSHostingController` object when you want to integrate OpenSwiftUI +/// views into an AppKit view hierarchy. At creation time, specify the OpenSwiftUI /// view you want to use as the root view for this view controller; you can /// change that view later using the ``NSHostingController/rootView`` property. /// Use the hosting controller like you would any other view controller, by @@ -26,10 +26,10 @@ public import AppKit open class NSHostingController: NSViewController where Content: View { var host: NSHostingView - /// Creates a hosting controller object that wraps the specified SwiftUI + /// Creates a hosting controller object that wraps the specified OpenSwiftUI /// view. /// - /// - Parameter rootView: The root view of the SwiftUI view hierarchy that + /// - Parameter rootView: The root view of the OpenSwiftUI view hierarchy that /// you want to manage using the hosting view controller. public init(rootView: Content) { // TODO @@ -39,11 +39,11 @@ open class NSHostingController: NSViewController where Content: View { } /// Creates a hosting controller object from an archive and the specified - /// SwiftUI view. + /// OpenSwiftUI view. /// /// - Parameters: /// - coder: The decoder to use during initialization. - /// - rootView: The root view of the SwiftUI view hierarchy that you want + /// - rootView: The root view of the OpenSwiftUI view hierarchy that you want /// to manage using this view controller. public init?(coder: NSCoder, rootView: Content) { // TODO @@ -70,7 +70,7 @@ open class NSHostingController: NSViewController where Content: View { preconditionFailure("init(coder:) must be implemented in a subclass and call super.init(coder:, rootView:)") } - /// The root view of the SwiftUI view hierarchy managed by this view + /// The root view of the OpenSwiftUI view hierarchy managed by this view /// controller. public var rootView: Content { get { host.rootView } @@ -87,23 +87,23 @@ open class NSHostingController: NSViewController where Content: View { } /// The options for how the hosting controller's view creates and updates - /// constraints based on the size of its SwiftUI content. + /// constraints based on the size of its OpenSwiftUI content. /// /// NSHostingController can create minimum, maximum, and ideal (content - /// size) constraints that are derived from its SwiftUI view content. These + /// size) constraints that are derived from its OpenSwiftUI view content. These /// constraints are only created when Auto Layout constraints are otherwise /// being used in the containing window. /// /// If the NSHostingController is set as the `contentViewController` of an /// `NSWindow`, it will also update the window's `contentMinSize` and - /// `contentMaxSize` based on the minimum and maximum size of its SwiftUI + /// `contentMaxSize` based on the minimum and maximum size of its OpenSwiftUI /// content. /// /// `sizingOptions` defaults to `.standardBounds` (which includes /// `minSize`, `intrinsicContentSize`, and `maxSize`), but can be set to an /// explicit value to control this behavior. For instance, setting a value /// of `.minSize` will only create the constraints necessary to maintain the - /// minimum size of the SwiftUI content, or setting a value of `[]` will + /// minimum size of the OpenSwiftUI content, or setting a value of `[]` will /// create no constraints at all. /// /// If a use case can make assumptions about the size of the @@ -112,7 +112,7 @@ open class NSHostingController: NSViewController where Content: View { /// fewer options can improve performance as it reduces the amount of layout /// measurements that need to be performed. If an `NSHostingController` has /// a `frame` that is smaller or larger than that required to display its - /// SwiftUI content, the content will be centered within that frame. + /// OpenSwiftUI content, the content will be centered within that frame. public var sizingOptions: NSHostingSizingOptions { get { host.sizingOptions } set { host.sizingOptions = newValue } diff --git a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift index e6fc7e527..d97858869 100644 --- a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift +++ b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift @@ -13,19 +13,19 @@ public import AppKit import OpenSwiftUI_SPI import COpenSwiftUI -/// An AppKit view that hosts a SwiftUI view hierarchy. +/// An AppKit view that hosts a OpenSwiftUI view hierarchy. /// -/// You use `NSHostingView` objects to integrate SwiftUI views into your +/// You use `NSHostingView` objects to integrate OpenSwiftUI views into your /// AppKit view hierarchies. A hosting view is an /// [NSView](https://developer.apple.com/documentation/AppKit/NSView) object that manages a single -/// SwiftUI view, which may itself contain other SwiftUI views. Because it is an +/// OpenSwiftUI view, which may itself contain other OpenSwiftUI views. Because it is an /// [NSView](https://developer.apple.com/documentation/AppKit/NSView) object, you can integrate it /// into your existing AppKit view hierarchies to implement portions of your UI. /// For example, you can use a hosting view to implement a custom control. /// -/// A hosting view acts as a bridge between your SwiftUI views and your AppKit +/// A hosting view acts as a bridge between your OpenSwiftUI views and your AppKit /// interface. During layout, the hosting view reports the content size -/// preferences of your SwiftUI views back to the AppKit layout system so that +/// preferences of your OpenSwiftUI views back to the AppKit layout system so that /// it can size the view appropriately. The hosting view also coordinates event /// delivery. @available(iOS, unavailable) @@ -34,22 +34,22 @@ import COpenSwiftUI @available(visionOS, unavailable) open class NSHostingView: NSView, XcodeViewDebugDataProvider where Content: View { /// The options for how the hosting view creates and updates constraints - /// based on the size of its SwiftUI content. + /// based on the size of its OpenSwiftUI content. /// /// NSHostingView can create minimum, maximum, and ideal (content size) - /// constraints that are derived from its SwiftUI view content. These + /// constraints that are derived from its OpenSwiftUI view content. These /// constraints are only created when Auto Layout constraints are otherwise /// being used in the containing window. /// /// If the NSHostingView is set as the `contentView` of an `NSWindow`, it /// will also update the window's `contentMinSize` and `contentMaxSize` - /// based on the minimum and maximum size of its SwiftUI content. + /// based on the minimum and maximum size of its OpenSwiftUI content. /// /// `sizingOptions` defaults to `.standardBounds` (which includes /// `minSize`, `intrinsicContentSize`, and `maxSize`), but can be set to an /// explicit value to control this behavior. For instance, setting a value /// of `.minSize` will only create the constraints necessary to maintain the - /// minimum size of the SwiftUI content, or setting a value of `[]` will + /// minimum size of the OpenSwiftUI content, or setting a value of `[]` will /// create no constraints at all. /// /// If a use case can make assumptions about the size of the `NSHostingView` @@ -57,7 +57,7 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont /// a fixed frame, setting this to a value with fewer options can improve /// performance as it reduces the amount of layout measurements that need to /// be performed. If an `NSHostingView` has a `frame` that is smaller or - /// larger than that required to display its SwiftUI content, the content + /// larger than that required to display its OpenSwiftUI content, the content /// will be centered within that frame. public var sizingOptions: NSHostingSizingOptions = .standardBounds { didSet { @@ -210,9 +210,9 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont sizingOptions.contains(.intrinsicContentSize) ? 3 : 0 } - /// Creates a hosting view object that wraps the specified SwiftUI view. + /// Creates a hosting view object that wraps the specified OpenSwiftUI view. /// - /// - Parameter rootView: The root view of the SwiftUI view hierarchy that + /// - Parameter rootView: The root view of the OpenSwiftUI view hierarchy that /// you want to manage using this hosting view. public required init(rootView: Content) { self._rootView = rootView @@ -454,7 +454,7 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont } } - /// The root view of the SwiftUI view hierarchy managed by this view + /// The root view of the OpenSwiftUI view hierarchy managed by this view /// controller. public var rootView: Content { get { _rootView }