我正在将SwiftUI用于Mac应用程序,其中主窗口包含侧边栏视图和详细信息视图。选择侧边栏中的项目时,它将更改在详细视图中显示的视图。详细视图中显示的视图的大小不同,这会导致窗口的大小在显示时发生变化。但是,当窗口更改大小时,在更改大小的过程中看起来非常“刺眼”。有没有办法平滑改变窗口大小的动画?
在详细视图中显示的视图:
// OneView.swift
struct OneView: View {
var body: some View {
VStack {
Text("One View").font(.headline).padding()
Text("Some stuff here.")
}
.frame(width: 400, height: 300)
}
}
// TwoView.swift
struct TwoView: View {
var body: some View {
VStack {
Text("Two View").font(.headline).padding()
Text("More stuff is here.")
}
.frame(width: 400, height: 500) }
}
// ThreeView.swift
struct ThreeView: View {
var body: some View {
VStack {
Text("Three View").font(.headline).padding()
Text("Another view goes here.")
}
.frame(width: 500, height: 200)
}
}
主要内容视图,详细信息视图和侧边栏视图:
// ContentView.swift
struct SidebarView: View {
@State private var selected = Set<String>()
private let items = ["One View", "Two View", "Three View"]
var body: some View {
List(items, id: \.self, selection: $selected) { item in
NavigationLink(destination: DetailView(selection: item)) {
Text("\(item)")
}
}
.listStyle(SidebarListStyle())
}
}
struct DetailView: View {
var selection: String
var body: some View {
switch selection {
case "One View":
return AnyView(OneView())
case "Two View":
return AnyView(TwoView())
case "Three View":
return AnyView(ThreeView())
default:
return AnyView(
Text("Some \(selection) view here").frame(maxWidth: .infinity, maxHeight: .infinity)
)
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
SidebarView()
DetailView(selection: "One View")
}
}
}
应用程序委托的内容是:
// AppDelegate.swift
@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 = NSWindow(
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)
}
}
可以在https://youtu.be/LcF7U6GW_RI上获得窗口更改大小的屏幕记录
答案 0 :(得分:1)
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: {
})
还应该提到ResizingView
和window
不必在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)
}
}
答案 1 :(得分:0)
这里是可行方法的演示。我是从另一种角度来看的,因为您需要重新设计解决方案才能采用它。
演示
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)
}
...
答案 2 :(得分:0)
以下内容无法解决您的问题,但可能(需要做一些额外的工作)会导致您找到解决方法。
我没有太多要进一步研究的内容,但是有可能覆盖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)
}
}