---
title: "SwiftUI not restoring the window size on macOS"
slug: swiftui-not-restoring-the-window-size-on-macos
section: tech
date: 2026-11-07T11:47:00.000Z
canonical: https://roland.leth.ro/blog/tech/swiftui-not-restoring-the-window-size-on-macos
description: "SwiftUI kept opening my Mac app at its default size instead of where I left it. The cause was a private ViewModifier at the window root; the fix was one word."
---

SwiftUI restores a Mac app's windows by itself: move the window, resize it, quit, and the next launch puts it back where it was. Apple's [Tailor macOS windows with SwiftUI][wwdc] treats that as the default, with `.restorationBehavior` mostly there to turn it off, and `defaultSize` only counts on the very first launch, when there's nothing to restore yet.

In [Continuum][continuum], a Mac app I make for keeping notes on the people you manage, it just didn't. The window opened at `defaultSize`, centered, on every single launch, no matter where I'd left it or how big I'd made it, and there was nothing in the logs to say why.

It had been like that from the start, and I only found out when a user told me the default size was too big on a 13" screen. A window that remembers its size would've made that a one-time resize.

I went through the usual suspects, more or less in this order:

- The "Close windows when quitting an application" setting in Desktop & Dock.
- Whether `defaultSize` was somehow winning over the restored frame.
- `NSWindow.setFrameAutosaveName`, which loses to SwiftUI: it applies `defaultSize` again once the window is already on screen.
- An AppKit store for the window's position, which never restored either.
- A `WindowSizeStore` of my own, saving the content size and feeding it back through `.defaultSize`. That one actually worked, and I reverted it once the real cause turned up.

All plausible, none of them the cause.

What actually helped was a pretty boring step — a new, empty project with the same scene modifiers restored its window just fine. So the question stopped being how the API works, and became what's different in _my_ app.

Next, the app itself. I stripped Continuum's window down to `Text("Hello")` and went through the modifiers one at a time, commenting them in and out. The one that made the difference was the app lock, a `ViewModifier` that covers the whole app with a lock screen (an optional Touch ID or password lock, off by default), and it kept breaking restoration even with its body cut down to nothing:

```swift
// ContinuumApp.swift
WindowGroup {
   ContentView()
      // [...]
      .appLockGate() // ❶
}
.defaultSize(width: .windowDefaultWidth, height: .windowDefaultHeight)

// AppLockGate.swift
private struct AppLockGate: ViewModifier { // ❷
   func body(content: Content) -> some View {
      content
   }
}

extension View {
   func appLockGate() -> some View {
      modifier(AppLockGate())
   }
}
```

A modifier that does nothing, applied straight to the window's content (❶), and the window still forgot its frame. With that little code, the one word left to look at was `private` (❷). I took it out, and the window came back where I'd left it.

Why would an access level change anything at runtime?

## Private types at the window root

A modifier applied directly to the `WindowGroup`'s content becomes part of the window's root view type, something like `ModifiedContent<…, AppLockGate>`. Modifiers inside `ContentView`'s `body` don't count, since they aren't part of `ContentView`'s own type; Continuum has a few private ones in there (`demoModeBanner`, `borderedCard`), and they're fine.

A private type's name isn't stable, though. We can check with `String(reflecting:)`, in a tiny command-line tool whose module is called `Probe`:

```swift
private struct Gate {}
fileprivate struct FileGate {}
struct Open {}

print(String(reflecting: Gate.self))     // Probe.(unknown context at $104b00c38).Gate
print(String(reflecting: FileGate.self)) // Probe.(unknown context at $104b00c5c).FileGate
print(String(reflecting: Open.self))     // Probe.Open
```

Run it again and the first 2 lines print `$102444c38` and `$102444c5c` instead: the binary is loaded at a different address on every launch, and the address is _in_ the name. `Open` stays `Probe.Open` every time.

I can't say for sure that this is what breaks restoration, since nothing documents how SwiftUI keys a window's saved state. But if it uses that type's name in any way, each launch would look for a frame the previous launch never saved, find nothing and quietly fall back to `defaultSize`, which is exactly what it looked like. It also means `fileprivate` is no fix, since its name has the same address in it; I'd keep anything applied at the window root at least internal.

`AppLockGate` is internal now, and I put this at the top of its doc comment, since every other `ViewModifier` in Continuum is private (as it should be) and the first access-level tidy-up would've put the bug right back:

```swift
/// **Internal, never `private` — do not "tidy" this.** [...]
struct AppLockGate: ViewModifier {
```

So, if a Mac app won't remember its window, check what's applied directly to the `WindowGroup`'s content before anything else, and make sure none of it is `private` or `fileprivate`.

If you know where this is documented, or why it fails without a single log line, I'm all ears: [@roland.leth.ro][] or [@rolandleth][]!

[wwdc]: https://developer.apple.com/videos/play/wwdc2024/10148/ "Tailor macOS windows with SwiftUI"
[continuum]: /projects/continuum "Continuum"
[@roland.leth.ro]: https://bsky.app/profile/roland.leth.ro "@roland.leth.ro on Bluesky"
[@rolandleth]: https://x.com/rolandleth "@rolandleth on X"
