我有一个程序,它使用具有 2 个导航目的地的导航堆栈。我不知道如何将每个视图框架在屏幕上居中,并根据显示的特定视图调整宽度和高度(所有 3 个视图框架都有不同的框架宽度和高度,2 个导航目的地视图比主视图大) .
所以我在网上找到了下面的代码。下面列出了链接。我已将代码集成到我的代码中。它允许我将初始主视图框架显示在所需尺寸的中心。
当我转到导航目的地视图时,视图框架大小会正确调整并居中。但是当我返回主视图时,框架大小/位置不会改变。如果我然后转到另一个导航目标视图,则框架大小仅在较大时才会更改。因此,视图框架尺寸/位置最终会达到显示的最大视图尺寸,但始终居中。
在我在网上找到的代码中,它在 NSWindow 的扩展中设置了框架原点。为了让主视图以所需的宽度和高度显示,我必须在 @main 中设置默认值,如下所示:
.defaultSize(width: 450, height: 700)
。由于这是设置默认大小,因此我应该能够返回主视图将帧大小(如果需要,还可以重置原点)重置为默认值。
我创建了一个函数
setHomePosition
,将其添加到 NSWindow 扩展中(请参阅下面的代码),并认为我可以在主视图(ContentView)中运行它onappear
,但 Xcode 找不到该函数。我认为因为它是 NSWindow 的扩展,而不是 View。
在查看主页视图(内容视图)和 2 个导航目标视图的代码时,我意识到每个目标视图都包含一个 .frame(),用于设置宽度和高度,但主页视图没有。我认为它是用 @main 中的 .default size 代码行设置的。因此,如果我能够确定如何在返回主视图时设置主视图的框架,我可能会有一个解决方案。如果有人能指出我正确的方向,我将不胜感激。
https://gist.github.com/ABridoux/b935c21c7ead92033d39b357fae6366b
@main
struct TableViewTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.hostingWindowPosition(
vertical: .center,
horizontal: .center,
screen: .main
)
}
.defaultSize(width: 450, height: 700)
}
}
extension NSWindow {
struct Position {
static let defaultPadding: CGFloat = 16
var vertical: Vertical
var horizontal: Horizontal
var padding = Self.defaultPadding
}
}
extension NSWindow.Position {
enum Horizontal {
case left, center, right
}
enum Vertical {
case top, center, bottom
}
}
extension View {
func hostingWindowPosition(
vertical: NSWindow.Position.Vertical,
horizontal: NSWindow.Position.Horizontal,
padding: CGFloat = NSWindow.Position.defaultPadding,
screen: NSScreen? = nil
) -> some View {
modifier(
WindowPositionModifier(
position: NSWindow.Position(
vertical: vertical,
horizontal: horizontal,
padding: padding
),
screen: screen
)
)
}
}
private struct WindowPositionModifier: ViewModifier {
let position: NSWindow.Position
let screen: NSScreen?
func body(content: Content) -> some View {
content.background(
HostingWindowFinder {
$0.setPosition(position, in: screen)
}
)
}
}
private struct HostingWindowFinder: NSViewRepresentable {
var callback: (NSWindow) -> ()
func makeNSView(context: Self.Context) -> NSView {
let view = BridgingView()
view.callback = callback
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
}
private class BridgingView: NSView {
var callback: ((NSWindow) -> ())?
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
if let window = window {
callback?(window)
}
}
}
extension NSWindow {
func setPosition(_ position: Position, in screen: NSScreen?) {
guard let visibleFrame = (screen ?? self.screen)?.visibleFrame else { return }
let origin = position.value(forWindow: frame, inScreen: visibleFrame)
setFrameOrigin(origin)
}
// I added this function
func setHomePosition() {
let nsRectangle: NSRect = NSRect(x: 1055.0, y: 370.0, width: 450, height: 700)
setFrame(nsRectangle, display: true)
}
}
extension NSWindow.Position {
func value(forWindow windowRect: CGRect, inScreen screenRect: CGRect) -> CGPoint {
let xPosition = horizontal.valueFor(
screenRange: screenRect.minX..<screenRect.maxX,
width: windowRect.width,
padding: padding
)
let yPosition = vertical.valueFor(
screenRange: screenRect.minY..<screenRect.maxY,
height: windowRect.height,
padding: padding
)
return CGPoint(x: xPosition, y: yPosition)
}
}
extension NSWindow.Position.Horizontal {
func valueFor(
screenRange: Range<CGFloat>,
width: CGFloat,
padding: CGFloat)
-> CGFloat {
switch self {
case .left: return screenRange.lowerBound + padding
case .center:
return (screenRange.upperBound + screenRange.lowerBound - width) / 2
case .right: return screenRange.upperBound - width - padding
}
}
}
extension NSWindow.Position.Vertical {
func valueFor(
screenRange: Range<CGFloat>,
height: CGFloat,
padding: CGFloat)
-> CGFloat {
switch self {
case .top: return screenRange.upperBound - height - padding
case .center: return (screenRange.upperBound + screenRange.lowerBound - height) / 2
case .bottom: return screenRange.lowerBound + padding
}
}
}
经过一些额外的研究,我确定可以通过在 @main 中为我想要的每个窗口大小/原点创建一个 WindowGroup 来获得所需的结果。所以我最终得到了一个主页、表格和图表窗口组。为了获得我想要的每个窗口组的大小/原点(都不同),我修改了上面的 setPosition NSWindow 扩展,为每个窗口组硬编码了一个 nsRectangle 并用 setFrame 分配它。这意味着为每个窗口创建一个位置逻辑文件。导航堆栈不再使用,取而代之的是一系列按钮,其操作是 openWindow(id:value:),当然使用多个窗口组意味着当选择一个按钮时会显示一个附加窗口。
//updated nswindow extension in position logic for home window group
func setPositionHome(_ position: Position, in screen: NSScreen?) {
let nsRectangle: NSRect = NSRect(x: 1055.0, y: 370.0, width: 450, height: 700)
setFrame(nsRectangle, display: true)
}
// updated @main
struct WindowGroupsApp: App {
var body: some Scene {
WindowGroup ("Home") {
ContentView()
.hostingWindowPositionHome(
vertical: .center,
horizontal: .center,
screen: .main
)
}
WindowGroup ("Table", id: "table", for: String.self) { $fundName in
TableView(fundName: fundName!)
.hostingWindowPositionTable(
vertical: .center,
horizontal: .center,
screen: .main
)
}
WindowGroup ("Chart", id: "chart", for: String.self) { $fundName in
ChartView(fundName: fundName!)
.hostingWindowPositionChart(
vertical: .center,
horizontal: .center,
screen: .main
)
}
}
}
// home view
struct ContentView: View {
@Environment (\.openWindow) private var openWindow
var body: some View {
VStack(alignment: .leading) {
ZStack (alignment: .leading) {
RoundedRectangle(cornerRadius: 10.0)
.fill(Color.white)
.frame(width: 200, height: 80)
.padding(.leading, 20)
VStack(alignment: .leading, spacing: 10) {
Button {
openWindow(id: "table", value: "Table")
} label: {
Text("Table")
.font(Font.custom("Arial", size: 14.0))
.foregroundColor(Color.blue)
.background(Color.clear)
.padding(.leading, 35)
}
.focusable(false)
.buttonStyle(PlainButtonStyle())
Button {
openWindow(id: "chart", value: "Chart")
} label: {
Text("Chart")
.font(Font.custom("Arial", size: 14.0))
.foregroundColor(Color.blue)
.background(Color.clear)
.padding(.leading, 35)
}
.focusable(false)
.buttonStyle(PlainButtonStyle())
}
}
}
.frame(width: 450, height: 600, alignment: .topLeading)
}
}