I have a MacOS MenuBar app with a custom rounded NSWindow:
class RoundedWindow: NSWindow {
override var contentView: NSView? {
didSet {
guard let contentView = contentView else { return }
contentView.wantsLayer = true
contentView.layer?.cornerRadius = 10
contentView.layer?.masksToBounds = true
}
}
}
class ClearBackgroundHostingController<Content>: NSHostingController<Content> where Content: View {
override func loadView() {
self.view = NSView()
self.view.wantsLayer = true
self.view.layer?.backgroundColor = NSColor.clear.cgColor
}
}
And a function in my AppDelegate to adjust the window size
func adjustWindowSize(to size: CGSize) {
guard let window = windowController.window else { return }
var newFrame = window.frame
newFrame.size = size
// Calculate the new Y position to keep the window top aligned.
let newY = window.frame.maxY - size.height
newFrame.origin.y = newY
NSAnimationContext.runAnimationGroup({ context in
context.duration = 10.0
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
window.animator().setFrame(newFrame, display: true)
}, completionHandler: nil)
}
I'm keeping track of whether or not the preferences window is open, and trying to update and animate the size transition accordingly:
static let largeSize = CGSize(width: 512, height: 288)
static let smallSize = CGSize(width: 220, height: 220)
@State var size: CGSize = Self.smallSize
var body: some View {
ZStack {
if appDelegate.showingPreferences {
PreferencesView(appDelegate: appDelegate)
} else {
if viewModel.isVPNAvailable || !viewModel.isVPNAvailable {
mainContentView
}
if !viewModel.isVPNAvailable {
vpnNotAvailableView
}
}
}
.onChange(of: appDelegate.showingPreferences) { _ in
withAnimation(.easeInOut(duration: 1)) {
let newSize = size == Self.largeSize ? Self.smallSize : Self.largeSize
size = newSize
appDelegate.adjustWindowSize(to: newSize)
}
}
.onReceive(appDelegate.vpnStatusPublisher.receive(on: DispatchQueue.main)) { newStatus in
actionInProgress = false
switch newStatus {
case .connected:
appDelegate.vpnIsConnected = true
case .connecting, .disconnecting, .disconnected:
appDelegate.vpnIsConnected = false
}
}
.onChange(of: appDelegate.showingPreferences) { _ in
withAnimation(.easeInOut(duration: 1)) { // Adjust duration as needed
size = size == Self.largeSize ? Self.smallSize : Self.largeSize
}
}
}
In order to match this CodePen animation: https://codepen.io/formfield/pen/zYeJzXz
However, I can't seem to actually get any animation to happen. Am I approaching this the right way?

I don't know if you've already found a solution, but I think the error is in how you animate the frame.
Window frames can be animated with
window.setFrame(newFrame, display: true, animate: withAnimation)(https://developer.apple.com/documentation/appkit/nswindow/1419519-setframe)The animation behaviour of this unfortunately cannot be adjusted with a
NSAnimationContext.runAnimationGroup. To adjust the duration you can use: