5

我将 SwiftUI 用于主窗口包含 NavigationView 的 Mac 应用程序。此 NavigationView 包含一个侧边栏列表。选择侧栏中的项目时,它会更改详细视图中显示的视图。详细视图中显示的视图大小不同,这会导致窗口大小在显示时发生变化。但是,当详细视图更改大小时,窗口不会更改大小以适应新的详细视图。

如何根据 NavigationView 的大小更改窗口大小?

我的应用程序示例代码如下:

import SwiftUI

struct View200: View {
    var body: some View {
        Text("200").font(.title)
            .frame(width: 200, height: 400)
            .background(Color(.systemRed))
    }
}

struct View500: View {
    var body: some View {
        Text("500").font(.title)
            .frame(width: 500, height: 300)
            .background(Color(.systemBlue))
    }
}

struct ViewOther: View {
    let item: Int
    var body: some View {
        Text("\(item)").font(.title)
            .frame(width: 300, height: 200)
            .background(Color(.systemGreen))
    }
}

struct DetailView: View {

    let item: Int

    var body: some View {
        switch item {
        case 2:
            return AnyView(View200())
        case 5:
            return AnyView(View500())
        default:
            return AnyView(ViewOther(item: item))
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
             List {
                 ForEach(1...10, id: \.self) { index in
                     NavigationLink(destination: DetailView(item: index)) {
                         Text("Link \(index)")
                     }
                 }
             }
             .listStyle(SidebarListStyle())
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

下面是示例应用程序在详细视图更改大小时的样子:

录屏

4

3 回答 3

4

这是可行的可能方法的演示。我是根据一种不同的观点来做的,因为你需要重新设计你的解决方案才能采用它。

演示

SwiftUI 动画窗口框架

1) 需要窗口动画调整大小的视图

struct ResizingView: View {
    public static let needsNewSize = Notification.Name("needsNewSize")

    @State var resizing = false
    var body: some View {
        VStack {
            Button(action: {
                self.resizing.toggle()
                NotificationCenter.default.post(name: Self.needsNewSize, object: 
                    CGSize(width: self.resizing ? 800 : 400, height: self.resizing ? 350 : 200))
            }, label: { Text("Resize") } )
        }
        .frame(minWidth: 400, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
    }
}

2)窗口的所有者(在这种情况下AppDelegate

import Cocoa
import SwiftUI
import Combine

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow!
    var subscribers = Set<AnyCancellable>()

    func applicationDidFinishLaunching(_ aNotification: Notification) {

        let contentView = ResizingView()

        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), // just default
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)
        window.center()
        window.setFrameAutosaveName("Main Window")
        window.contentView = NSHostingView(rootView: contentView)
        window.makeKeyAndOrderFront(nil)

        NotificationCenter.default.publisher(for: ResizingView.needsNewSize)
            .sink(receiveCompletion: {_ in}) { [unowned self] notificaiton in
                if let size = notificaiton.object as? CGSize {
                    var frame = self.window.frame
                    let old = self.window.contentRect(forFrameRect: frame).size

                    let dX = size.width - old.width
                    let dY = size.height - old.height

                    frame.origin.y -= dY // origin in flipped coordinates
                    frame.size.width += dX
                    frame.size.height += dY
                    self.window.setFrame(frame, display: true, animate: true)
                }
            }
            .store(in: &subscribers)
    }
    ...
于 2019-12-18T07:22:08.723 回答
3

Asperi 的回答对我有用,但动画不适用于 Big Sur 11.0.1、Xcode 12.2。值得庆幸的是,如果你将动画包装在 NSAnimationContext 中,动画就可以工作:

NSAnimationContext.runAnimationGroup({ context in
    context.timingFunction = CAMediaTimingFunction(name: .easeIn)
    window!.animator().setFrame(frame, display: true, animate: true)
}, completionHandler: {
})

还应该提到的是ResizingViewwindow不必在 AppDelegate 内部初始化;你可以继续使用 SwiftUI 的 App 结构:

@main
struct MyApp: App {

    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ResizingView()
        }
    }
}

class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow?
    var subscribers = Set<AnyCancellable>()

    func applicationDidBecomeActive(_ notification: Notification) {
        self.window = NSApp.mainWindow
    }

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        setupResizeNotification()
    }

    private func setupResizeNotification() {
        NotificationCenter.default.publisher(for: ResizingView.needsNewSize)
            .sink(receiveCompletion: {_ in}) { [unowned self] notificaiton in
                if let size = notificaiton.object as? CGSize, self.window != nil {
                    var frame = self.window!.frame
                    let old = self.window!.contentRect(forFrameRect: frame).size
                    let dX = size.width - old.width
                    let dY = size.height - old.height
                    frame.origin.y -= dY // origin in flipped coordinates
                    frame.size.width += dX
                    frame.size.height += dY
                    NSAnimationContext.runAnimationGroup({ context in
                        context.timingFunction = CAMediaTimingFunction(name: .easeIn)
                        window!.animator().setFrame(frame, display: true, animate: true)
                    }, completionHandler: {
                    })
                }
            }
            .store(in: &subscribers)
    }
}
于 2020-11-28T06:28:12.777 回答
2

以下内容不会解决您的问题,但可能(通过一些额外的工作)引导您找到解决方案。

我没有太多要进一步研究的东西,但是可以覆盖 NSWindow 中的 setContentSize 方法(当然是通过子类化)。这样您就可以覆盖默认行为,即设置没有动画的窗口框架。

您必须弄清楚的问题是,对于像您这样的复杂视图,setContentSize 方法被重复调用,导致动画无法正常工作。

以下示例运行良好,但那是因为我们正在处理一个非常简单的视图:

在此处输入图像描述

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow!


    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Create the window and set the content view. 
        window = AnimatableWindow(
            contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)
        window.center()
        window.setFrameAutosaveName("Main Window")
        window.contentView = NSHostingView(rootView: contentView)
        window.makeKeyAndOrderFront(nil)
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
}


class AnimatableWindow: NSWindow {
    var lastContentSize: CGSize = .zero

    override func setContentSize(_ size: NSSize) {

        if lastContentSize == size { return } // prevent multiple calls with the same size

        lastContentSize = size

        self.animator().setFrame(NSRect(origin: self.frame.origin, size: size), display: true, animate: true)
    }

}

struct ContentView: View {
    @State private var flag = false

    var body: some View {
        VStack {
            Button("Change") {
                self.flag.toggle()
            }
        }.frame(width: self.flag ? 100 : 300 , height: 200)
    }
}
于 2019-12-19T11:25:52.057 回答