Streaming is available in most browsers,
and in the Developer app.
-
Tailor macOS windows with SwiftUI
Make your windows feel tailor-made for macOS. Fine-tune your app's windows for focused purposes, ease of use, and to express functionality. Use SwiftUI to style window toolbars and backgrounds. Arrange your windows with precision, and make smart decisions about restoration and minimization.
Chapters
- 0:53 - Anatomy of a window
- 1:53 - App window structure
- 2:41 - Style window toolbars
- 3:53 - Refine window behaviors
- 5:55 - Adjust window placement
Resources
- Customizing window styles and state-restoration behavior in macOS
- Destination Video
- Forum: UI Frameworks
- Windows
Related Videos
WWDC24
WWDC22
-
Download
Hi, I am Haotian, an engineer on the SwiftUI team, and in this video, I am going to share how to use new SwiftUI APIs to tailor your macOS application windows.
Apple revolutionized the desktop experience in 1984 by making it digital. From monochrome, colorized, aqua, and beyond, it has remained the heart of the macOS experience.
Windows are a fundamental unit for drawing the user interface of an application.
A typical window has a few recognizable components.
At the top of the window, there is a toolbar to host the window controls, the title, and toolbar items. The main content of the app sits behind that and the background sits at the very back, casting a shadow to give a sense of layering and depth. I am going to show you how to customize these elements in your windows with an app called Destination Video.
It is a SwiftUI app built with Scenes like Window and WindowGroup to organize and play videos. If you want to know more about these different scene types, check out "Bring multiple windows to your SwiftUI app", and "Work with windows in SwiftUI".
Now, I will use new SwiftUI APIs to further tune the windows for my app's content.
There is the main window, which is the primary interface for navigating through video collections.
The about window, for showing the application version and support information.
And the video player window, for media playback.
I will customize each of these windows for the kind of content they present by applying changes to window toolbars, window behaviors, and window placements. I will start with customizing the toolbar of the main window in Destination Video.
It is created from a WindowGroup with a content view.
Right now, the window shows the toolbar and title. My design features a large image below them. To highlight the image, I remove both the title and the toolbar background using the .toolbar(removing) modifier, And the .toolbarBackgroundVisibility() modifier.
And now, the large image extends to the top edge of the window. Even though the title is hidden from the user interface, it is still associated with the window. It will be used by accessibility, and other features. For example, the main menu item will continue to show the window title.
With just a few lines of code, I have customized the toolbar elements to fit my design, with an emphasis on the window content. Next, I will refine the behaviors of the About window.
Every Mac app has an About window, accessed from the main menu. It shows the app’s version number and other details. For Destination Video, I’ve decided to make a custom About window that better matches the main catalog window’s minimal look and feel.
To get started, I add some of the same modifiers that I used in the previous section to hide the title and remove the toolbar background.
I use the .containerBackground() modifier to replace the window background color with a material, giving the window a little extra style. Materials are partially transparent, like frosted glass, allowing color from the desktop to shine through.
The green Zoom window control was disabled because the content has a fixed size. The About window displays static information and is always reachable from the main menu, so I would like to disable the yellow Minimize control.
I use the .windowMinimizeBehavior() modifier to disable it.
I am also going to customize the state restoration behavior of the About window. State restoration saves the size and position of windows when the app quits. Then, it re-opens them automatically the next time the app launches. Windows in SwiftUI apps are restored automatically. But the About window should not. So I disable this with the .restorationBehavior() scene modifier. Now, I’ve got my windows looking great and behaving nicely. Last, I will adjust exactly where they appear on screen.
This is especially important for the video player window. Its ideal size depends on the video it is playing, and the screen it is playing on, highlighted by the blue solid wire frame and the yellow dotted wire frame respectively. The videos come in a variety of sizes. Some are very small and fit easily onto the screen. Others are larger than the screen, in one or both dimensions whether because the video uses a vertical format, or the screen does. Playback needs to work with any number of external monitor configurations.
My goal with the video player window is to use the best position and size, given the video, and the screen it is playing on.
The first thing I do is specify the initial placement of a newly created player window, using the .defaultWindowPlacement modifier.
This modifier takes a closure with two parameters: a proxy view for querying the size of the content, and a context value with information about the display. I compute the window’s ideal size by calling .sizeThatFits() on the content. And I acquire the usable region by getting the visibleRect from the display, which automatically accounts for any space taken by the menu bar and dock. The video should be shown at its native size, but if it is too big for the screen, it should be scaled down to fit. I am returning a placement with this size and no explicit position. The window will appear centered by default.
I also need to consider the different ways video player windows can be resized while they are open.
For example, choosing Zoom from the Window menu automatically makes a window larger. Choosing Zoom again will restore the window to its previous size.
The .windowIdealPlacement modifier controls how large a zoomed window gets. My goal is to ensure that a zoomed window is as large as it can be while preserving its ideal aspect ratio.
The closure I provide is similar to the one for the ideal placement. As before, I use the context value to compute the display’s visible region. This time I want the window to grow to fit the display. I use my zoomToFit helper function for this.
With these few changes, the video player window feels much more aware of its content and the display.
By customizing my app windows, each one now feels purpose built for its task.
A few new APIs can go a long way. There are a lot more window customizations to explore in macOS Sequoia. For example, you can build borderless windows using the plain window style.
Or, create experiences like welcome windows that conditionally show first on launch.
These are just a few of the possibilities, and I hope they will inspire your own projects. Going forward... Style your toolbars to complement the content and purpose of your windows. Refine window behaviors to match the function of your app’s windows. Fine tune window placement to provide a great experience across all display configurations. Check out SwiftUI documentation and experiment with new APIs to reflect your design goals.
Thanks for watching, I wonder if you are watching me in a borderless video window!
-
-
3:11 - Style Toolbars - Removing Title
.toolbar(removing: title)
-
3:14 - Style Toolbars - Removing Toolbar Background
.toolbarBackgroundVisibility(.hidden, for: .windowToolbar)
-
4:33 - Refine Behaviors - Adding Container Background
.containerBackground(.thickMaterial, for: .window)
-
5:13 - Refine Behaviors - Minimize Behavior
.windowMinimizeBehavior(.disabled)
-
5:44 - Refine Behaviors - Restoration Behavior
.restorationBehavior(.disabled)
-
7:11 - Adjust Placement - Default Placement
.defaultWindowPlacement { content, context in var size = content.sizeThatFits(.unspecified) let displayBounds = context.defaultDisplay.visibleRect // modify size based on display bounds return WindowPlacement(size: size) }
-
8:35 - Adjust Placement - Ideal Placement
.windowIdealPlacement { content, context in var size = content.sizeThatFits(.unspecified) let displayBounds = context.defaultDisplay.visibleRect // modify size based on display bounds return WindowPlacement(size: size) }
-
9:48 - Borderless Window
.windowStyle(.plain)
-
9:53 - Default Launch Behavior
.defaultLaunchBehavior(.presented)
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.