Streaming is available in most browsers,
and in the Developer app.
-
Optimize the interface of your Mac Catalyst app
Discover how to tailor your Mac Catalyst app so that it looks and feels even more at home on the Mac by using the new “Optimize Interface for Mac” option in Xcode. Explore new layout and appearance options for Catalyst apps, and learn how they can provide you with graphical performance gains, sharper text, and an interface designed specifically for Apple's desktops and laptops. We'll show you how to take advantage of these options and provide best practices for organizing your code when developing for multiple platforms. Developers actively working on a Mac Catalyst project will get the most out of watching this session. If you're new to Catalyst, we recommend watching “Designing iPad Apps for Mac” and "Introducing iPad Apps for Mac" for an introduction. For more on working with Mac Catalyst, check out "What's new in Mac Catalyst”
Resources
- Adding Menus and Shortcuts to the Menu Bar and User Interface
- Human Interface Guidelines: Mac Catalyst
- Mac Catalyst
- Optimizing your iPad app for Mac
Related Videos
WWDC21
WWDC20
- Build with iOS pickers, menus and actions
- Design with iOS pickers, menus and actions
- What's new in Mac Catalyst
- What's new in SwiftUI
WWDC19
-
Download
Hello and welcome to WWDC.
Hello. My name is Nick Teissler, and I'm a Cocoa Engineer. Later, I'll be joined by my colleague, Jake Carter.
Today, we're going to tell you how to optimize the interface of your Mac Catalyst app.
The next level of Mac Catalyst app is what we call "Optimized for Mac." Optimizing for Mac gives you Mac-like visuals and controls that aren't available to Catalyst apps scaled to match the iPad.
Opting in to optimized for Mac is a build setting change that takes effect at launch time. An app optimized for Mac launches in the Mac idiom. Catalyst apps scaled to match the iPad launch in the iPad idiom.
This particular setting won't have any compile time implications. So you won't have build errors or framework issues if you're optimizing a Catalyst app scaled for iPad.
And we recommend you start with a Catalyst app that has already gone some work to bring it to the Mac. There's a lot you should do before optimizing for the Mac, like adding menu bar actions with keyboard shortcuts or handling right clicks in your interface with the UIContextMenuInteraction APIs.
See these sessions from WWDC 2019 that review features you can add to your Catalyst app before optimizing the interface for Mac. So let's look at what we mean when we say "Optimized for Mac". What exactly is getting optimized? Optimizing for the Mac is all about more Mac-like visuals. So, when you bring your iPad app over as an optimized Catalyst app, we render that content 1:1.
Compare that to the 77% downscale when Catalyst apps are adjusted to match your iPad views. In that case, 100 points in your code becomes 77 points onscreen.
When optimized for Mac, 100 points in your code is always equivalent to 100 points onscreen. Not scaling your content paves the way for many more visual improvements throughout an app.
If you use controls like UIButtons, UISlider, UISwitch, UIActivityIndicator and more, these will all render as macOS system controls. Here, you see a system iOS button that will render as a system Mac button when optimized.
These new control appearances will have sizes and metrics matching controls on the Mac. It's likely they won't be the same size as your iPad appearance. So your layouts might be a little bit altered.
Catalyst Optimized for Mac adjusts text style sizes like body, title and callout to match the Mac. Unlike Catalyst apps scaled for iPad, the rendered text is no longer scaled. The text style sizes are different.
For example, the body text style returns 17 points on Catalyst apps scaled to match the iPad, whereas it returns 13 points on Catalyst apps optimized for Mac. The true font sizes will look noticeably sharper than the scaled font sizes.
This also means if you have hardcoded font sizes, these will not be adjusted, and your UILabel or UITextField might feel sad. A small part of it, probably about 23%, won't feel home when it's on the Mac.
A final more straightforward change to be aware of is that Auto Layout will use Mac spacing if you're building constraints with the system spacing methods. The system spacing is typically larger on the Mac than in iOS.
So these are the high-level features that you will get if you choose to optimize your Mac Catalyst app for the Mac.
It may take a minute to mentally position Catalyst optimized for Mac in the growing set of options available to you to bring apps to the Mac. Catalyst Optimized for Mac is an alternative to Catalyst scaled to match the iPad. You can't have both. Let's compare the two more directly.
So, first, one doesn't obsolete the other. Scaling to match the iPad might certainly be a better fit for your app, and we'll continue to see great apps running in both idioms.
The scaled option is a fast path to a Mac app. It will get your app running on the Mac. You shouldn't need to refactor all of your app's views to get a great version of your app on the Mac. And that's part of the philosophy behind scaled apps. So you make some trade-offs to get your Mac app up and running quickly. The trade-offs you make might be more or less desirable depending on the specifics of your app. Optimizing for Mac is a possible next step.
A scaled app favors compatibility with your iPad app. Catalyst does everything it can do so you don't have to make app-wide code changes to get an appearance that fits in great on the Mac.
An optimized app favors an authentic Mac look and feel. Catalyst swaps in Mac controls and unscaled text sizes to make an app's interface look its absolute best on the Mac.
Because scaled apps favor this compatibility with the iPad version of your app, your layouts are preserved as much as possible. Most times, you don't have to make any changes for your view to lay out properly on the Mac.
But given that optimizing gives you a completely different look for controls, you'll need to do some layout work where you've made implicit assumptions of the sizes of controls. Now that we understand what is optimized and the differences between the idioms, we should start thinking about which apps can gain the most from opting in. Which apps want to be optimized to look their best? Apps that show lots of text can see noticeable visual improvements. This is because they get the true text sizes instead of scaled text.
Swift Playgrounds is a Catalyst app optimized for Mac this year on Big Sur. The rich text editor in Swift Playgrounds drives the app's visual appeal, so it makes sense to undertake the work and see these benefits.
Apps with certain emphases on graphics have potential for improvements too.
Graphics intensive apps that use Metal or SceneKit saw significant improvement in our experience. Swift Playgrounds uses SceneKit in their "Learn to Code" series. After optimizing for Mac, Swift Playgrounds saw higher frame rates and lower power consumption on Macs with both integrated and discrete graphics systems.
Another type of graphics emphasis is highly custom, detailed artwork. The differences between scaled Catalyst assets and optimized ones is identical to the visual differences you see if you display a png scaled to fit a particular size rather than at its natural dimensions. You can quickly evaluate how your app will look comparing the two versions side by side. The artwork in MapKit is a great example of this. MapKit has literally a world's worth of custom designs and icons. Looking at the magnified versions of the pixel-perfect iconography created for place marks, we can see the difference between scaled and unscaled artwork.
On the left here is the scaled icon that the California Academy of Sciences uses. On the right is the unscaled image. See how you can count the windows and pillars at the front of the building. Some of that fine detail is lost in a scaled graphic.
Just down the road is the de Young Museum. Here, too, the unscaled asset and even the text is sharper.
Finally, the Guggenheim Museum. The rounded architecture is represented more fully in the unscaled MapKit asset.
Icons are rendered much smaller on the screen, so the visual differences aren't as obvious to users as we've displayed them here.
But this still generalizes throughout MapKit. In dense cities like Paris, at a low zoom, having that resolution to show detailed road networks is a strong motivator for optimizing your Catalyst app if you use MapKit prominently. Another trait that can make an app a good candidate is having many control views. Like a popover showing some adjustable value. That view would likely fit in better if optimized for Mac. Maps takes advantage of this, using the checkbox here, which fits in better than a sliding switch might. Finally, the state of your third-party dependencies could be a limiting factor for your apps' candidacy. For all of you framework developers out there, supporting a Catalyst target might not be enough. Especially if your framework is UI-focused, you likely need to support optimizing for the Mac, so you don't limit your clients. Jake will show you how to make changes to your code to support the Mac idiom later. Now we'll look at how optimizing for Mac affects a view from an app scaled to match the iPad. On the left is a simple view showing the beginning of a recipe for a tart. The layout is built with stack views and contains five elements: a button, a label, a slider, an image and a text view. Watch closely as I optimize this for Mac without making any layout adjustments.
Phew, looks like our layout held up okay.
The first difference that jumps out to you is probably the system iOS button on the left changing to the system Mac button on the right. This causes the button to have a different size at run time and, depending on how you layout your views, possibly a different origin.
Let me draw your attention to the windows. They're exactly the same size in points on the Mac screen.
Remember, though, the point sizes of the window on the left and all of its contents are actually 77% of the sizes in your app's code.
This is the behavior from the first release of Catalyst on Catalina, but keep that in mind as we go through the rest of the changes.
Next, take a look at the leading edge of each window. The space between the leading edge of the window and the text view is noticeably smaller on the left than on the right. As we saw before, system spacing values are generally a bit larger on the Mac.
This means, though, that there is more horizontal padding on the view optimized for Mac. This gives that view less available width to lay out in. And this is the reason the text has wrapped an additional line, and the reason the slider is more narrow.
Strangely, though, even given less available width, the image view has taken up more space when optimized for Mac. It's laid out with required width and height constraints matching its intrinsic content size. Knowing that, you can guess why it's wider when optimized for Mac. The image has gone from being 77% of its natural size to 100% of its natural size.
You can handle this different ways depending on your circumstances. The optimal way is using Asset Catalogs to provide a Mac or iPad-specific asset. If you don't have a properly-sized asset, you can use Interface Builder's trait variations to assign a different size to either idiom. This fully explains the appearance of the slider. The slider is configured to take up the available space remaining. The increased padding and wider image have decreased the space available to the slider.
In fact, the optimized for Mac slider is shorter by the sum of the increased padding and the increased width of the image.
That's enough layout for now. Let's take a look back at appearances.
The slider on the left is a system UISlider with no customizations. On the right, you see it's adopted the appearance of the newly-designed macOS Slider.
Notice some subtle differences in the track height and knob shadow. The important part for you and the people who use your app is that this slider is identical to other sliders on the Mac. One thing that isn't obvious from these screenshots is the difference in text rendering. The text view is using the body text style. On the left, that will be a font size of 17 points, the iPad standard. On the right, 13, the Mac standard. As we know, the fonts match visually in size. However, since the view optimized for Mac is using a true 13-point font, the text is sharper. Remember that the additional line of wrap is because of the decreased horizontal width available and doesn't have anything to do with the text rendering or sizing. If you're inclined toward concrete numbers, I've annotated this slide with the layout values so you can convince yourself that all of these changes make sense.
Taking all of this together, you can see that you are committing to a certain amount of work refactoring even some simple layouts when you elect to optimize for the Mac.
Let's tidy up this Mac layout.
That app is truly at home on the Mac. So we just saw that it's less about applying rules of thumb to your layouts and more about understanding which elements will be changing, and how. The payoff is this wonderful app that looks completely at home to users.
Before we leave this view behind, take a look at that "Get Cooking" button on the left. Its tint color is set to systemTeal, but on the right, that tint color doesn't carry over to the Mac appearance at all. Tinted buttons aren't standard on the Mac and users aren't accustomed to them, so that tint color gets dropped for the optimal Mac appearance. In fact, there's a larger category of control customizations that are unavailable when optimizing for Mac, either because the customization is typical to the iPad and not the Mac, or because their usage isn't supported with the Mac visual appearance.
It's common to attach a gesture recognizer to a UIButton to detect a long press and overload the button with multiple actions. Your gesture recognizers on UIButtons will not get called if you're using a system button that has adopted the Mac appearance. You can handle this in a number of ways. First, make sure you aren't bringing an iPad interaction over to the Mac. This might be better handled with a menu item in the app's menu bar, or by attaching a menu to the UIButton using the UIButton menu property like Maps does here. When optimizing your app, we recommend auditing controls customized at the event handling level. This includes gesture recognizers on controls like UIButtons, and overrides of UIControl event tracking methods like beginTrackingTouchWithEvent. If the gesture recognizer is non-negotiable, remember that the custom button type is not rendered with the Mac appearance and will maintain the same event tracking as on the iPad and your gesture recognizer won't lose functionality. Look at this customized slider to the right, here. I've really taken off with it. It has a white minimumTrackTint color, and blue maximumTrackTintColor, and has replaced the circular thumb with an airplane.
You might experience some turbulence when you bring this to the Mac. In the Mac idiom, these customizations are unavailable. An effective mental model here is that when using system controls, you are limited to appearance customizations where standard Mac appearances overlap available UIKit API.
I'll now hand it over to Jake who will take you through making some changes to a scaled Catalyst app to optimize it for the Mac.
Thanks, Nick. Hi, my name is Jake, and I'm an engineer on the Xcode team. Today I'd like to show you a simple Mac Catalyst cookbook app that some of us have been working on. Let's jump right into a demo.
First, let me give you a quick tour of the app before we optimize it for the Mac. On the left, we have a sidebar with easy access to common areas of our app, like "All Recipes" and "Favorites." Next, we have a list of recipes, and finally, the recipe itself. We can add new recipes by pressing this "New Recipes" button.
Here, we can add details of our recipe along with a photo. This app already feels pretty good on the Mac, but there are some things that feel out of place, like the iOS-style button and the navigation bar. Let's switch over to Xcode and get started.
This is the target's general settings. Here you'll notice this new pop-up button next to the "Mac" checkbox.
By default, "Scale Interface to Match iPad" will be selected. New with Xcode 12 and Mac OS 10.16 is the "Optimize Interface for Mac" option.
Let's choose that.
What this will do is tell UIKit that we want our app to run with the Mac idiom which will give it a more Mac-like look and feel. Let's Build and Run to see what's changed.
I've set this up so that we can view both the "Scale Interface to Match iPad" and the "Optimize Interface for Mac" versions at the same time. Immediately, you'll notice that the window is larger. But not only that, the contents inside the window is also larger, and that's because it's no longer being scaled.
The next thing I'd like to point out is this "Timers" button. Just by making the change to "Optimize Interface for Mac," we're automatically getting a more Mac-like look for this button. There's also something else going on here. Not only does the button look bigger, because it's no longer being scaled, it's also using different metrics. What do I mean by that? What I mean is that some of the sizes have actually changed. For example, the font is bigger, and there's more padding between the text and the button border.
Because of these types of changes, the frame of the button is actually larger. It's these changes in metrics that you should keep in mind when auditing your layouts after making this change. Okay, let's look a little closer at this list of recipes. We know that the recipe images on the left and the "Favorite" icon on the right are slightly bigger, because they're no longer being scaled. But if you look closer, you may notice that the size of the text did not change. And that's because we've used a dynamic text style which automatically adjusts for optimal legibility. The size of the text looks good, but I think the list would look better if the recipe images and the "Favorite" icon were a little smaller, like they were before. Let's head back into Xcode and get this list looking better.
This is the view controller that manages that list of recipes. If we look through the code here that creates the layout, we can see that we're using an absolute height of 100 points.
Because of the way I've set up the Auto Layout constraints, if this height were smaller, it would cause the recipe images to be smaller, too.
I was playing around with this earlier and found that 80 points looks really nice.
So I'll update this line of code to check the idiom. If it's "Mac," we set the height to 80 points, otherwise we leave it at 100. Now for the Favorite icon.
For that I've specified an image here in the Asset Catalog. I've only filled in the "Universal" version, so it's getting used everywhere the app can run. In this case, on iPhone, iPad and Mac. As we saw, this version of the image looked too big next to the text when running on the Mac. Now that our app is running in the Mac idiom, it'll have access to the Mac assets defined here in the Asset Catalog. This will let us specialize the image and use one that's been handcrafted to the correct size.
To enable the Mac assets, all we need to do is click this "Mac" checkbox here in the "Devices" section of the inspector. Now we can drag in the Mac versions of our icon.
That's it. Let's Build and Run and take a look at that list again. Now you can see that our cell height is shorter, which caused our recipe images to be smaller also. And that our Favorite icon on the right is a more appropriate size for the text because it's using that specialized Mac asset. Let's move on and take a look at the "New Recipe" screen.
Okay, we got that new Mac style for this "Choose Photo" button, which is really nice. But there are some things here that feel out of place on the Mac, like this top navigation bar. Navigation view controllers and their navigation bars are really useful for transitioning between child view controllers, so that you can show more data on small screens. This type of interaction feels out of place on a Mac, though. It's also common to put frequently used buttons in the navigation bar. On a Mac, the toolbar, up here where you see the window title, is a better choice for this type of UI. So, the "Save" and "Cancel" buttons should be moved, either up to the toolbar, or down into the view and the navigation bar should go away completely. Because the "Save" and "Cancel" buttons are acting on the data entered in this view, and because both of them will end up dismissing the window, it would be more Mac-like to move them to the bottom of this view. I also see a bug. Now that we're running in the Mac idiom, the "Title" label is bigger than the other two labels. Let's go back to Xcode and get these fixed up.
This is the RecipeEditorViewController which is used in that "New Recipe" window. What I'd like to do is hide the navigation bar when running on the Mac.
To fix that, we'll check the idiom. If it's Mac, we hide the navigation bar. Pretty simple. Next, let's take a look at the storyboard.
Now that our project supports the Mac idiom, you'll notice this new idiom chooser in the device bar here at the bottom.
Watch the view above as I switch it to the "Mac." You'll notice the canvas has updated. The controls are now more Mac-like, and are using the Mac standard spacing and metrics.
The first thing I'd like to fix here is the "Title" label. At run time, it was bigger than the other two.
If we take a look at the "Font" attribute in the inspector, you can see that it's set up to use a 17-point system font. Like Nick mentioned earlier, this was getting scaled down to around 13 points. But now that we've chosen the "Optimize Interface for Mac" option, it's getting rendered at its unscaled 17 points. So that's why this one is bigger, but why haven't the other two changed? If we select one of the other two labels, we can see that it's set up to use the "Body" text style. Like I mentioned earlier, these text styles are dynamic and will adjust to be the appropriate size for the platform they're rendered on. We highly recommend using these text styles when you can. Let's go ahead and update the "Title" label to use the "Body" text style as well.
Next, let's address the "Save" and "Cancel" buttons. Remember that we've already hidden the navigation bar in code, so even though it does show up here, it won't at run time. Let's add new "Save" and "Cancel" buttons to the bottom of this view and set them up to only show in the Mac idiom.
First, I'll resize this bottom text view to make room for our new buttons.
When I do, Interface Builder alerts me to the fact that the frames in the canvas no longer match the frames computed by Auto Layout. I'll leave this for now so we have room for our buttons.
Now, I'll go ahead and drag out a stack view...
and pin it to the bottom right.
I'll adjust its spacing to use the system standard spacing...
and set its "Distribution" to "Fill Equally" so that the buttons are the same size.
Next, I'll drag out our buttons and update their titles.
I only want these buttons to show up when the app is running in the Mac idiom. So I'll select the containing stack view...
and I'll use this "Installed" attribute here at the bottom of the Attributes inspector.
For a view, this attribute specifies whether or not it will be added to the view hierarchy at run time. By having "Installed" checked, this stack view will always be added to the view hierarchy, but we can add a trait variation by clicking the plus button down here on the left.
This will let us vary the attribute based on these traits. New in Xcode 12, we've added the ability to vary the traits based on the Mac idiom. I'll go ahead and add a variation based on these traits.
We'll leave "Installed" checked for this new Mac idiom variation, but uncheck it for the "Default" case.
Now these views will only be installed when running in the Mac idiom.
Next, let's make sure that our text view doesn't cover up our new buttons.
To do that, I'll Control-drag from it to the stack view.
When I hold the "Option" key on the keyboard, the top choice changes from "Vertical Spacing" to "Vertical Standard Spacing." I'll choose that one.
The constraint was added, but some of our other constraints turned red. This means we have unsatisfiable constraints. If we look at the red constraints at the bottom, you can see that we still have that 20pt spacing between the text view and the bottom of its superview. This is conflicting with the new constraint we just added.
We can't just delete this old constraint, because when running on the iPad, our new buttons won't be installed, and we'll need some other way to specify the height of this text view. There are a couple ways we could fix this, but I think what I'll do is change the constraint's "Priority." Currently it's set to "Required," but let's change it to "High Priority" instead. This way it'll be ignored when it can't be satisfied.
We're back to all orange constraints, so all we need to do is click the "Update Frames" button down here in the bottom right.
Now the constraints work in both Mac and iPad idioms, and we can verify that using the idiom chooser in the device bar again.
Now when I bring up the "New Recipe" window, you can see that the "Title" label is the appropriate size, the navigation bar is hidden, and we have the "Save" and "Cancel" buttons at the bottom. This feels much more Mac-like. Let's go back to slides and recap.
So, by choosing the "Optimize Interface for Mac" option, you're telling UIKit you want your app to run in the Mac idiom. As we saw, this gives your application a more Mac-like look and feel. But it also means layout changes, because the Mac standard spacing and control metrics are being applied. This means that controls, image views and font sizes will be different. So you really need to audit your layouts after making this change. One tip would be to check for "unsatisfiable constraint" warnings in the logs. This can help find issues caused by the changes to control sizes. We also looked at providing Mac assets in the Asset Catalog to help with some of these kinds of issues.
One thing to keep in mind here is that if Mac assets can't be found, the system will fall back through "Mac Scaled" and "iPad" assets before using the "Universal" assets. As we saw in the demo, it's likely the sizes of these assets won't be optimal, so make sure to audit your images and provide Mac versions where appropriate. Lastly, we looked at some paradigm differences between Mac and iPad, like the navigation bar and button placement. You should keep these kinds of differences in mind when designing your Mac Catalyst apps.
I would highly recommend reading our Human Interface Guidelines for more insight into designing apps that feel truly great on the Mac.
While it is possible to back deploy a Mac Catalyst app that has been optimized for the Mac, there are some things you should keep in mind. When running on Catalina, these apps will not get the new Mac-like look and feel. It also means supporting both the iPad and Mac idioms. We found that a majority of the work when back deploying is making sure that your layouts can handle the metrics in both idioms. You'll also need to continue guarding uses of frameworks and API that aren't supported on the Mac.
Consider checking the idiom on traitCollection...
or using conditional compilation blocks to help with these sorts of things. Keep in mind that the conditional compilation block here will be true regardless of idiom.
And in some cases, it may be appropriate to use both techniques.
That's it for me. I'll hand it back over to Nick to tell you about SwiftUI optimized for Mac. Nick. Thanks for the demo, Jake. That Cookbook app fits right in on the Mac now. You may know that SwiftUI is available to Catalyst applications scaled to match iPad. SwiftUI is also available to use in applications optimized for Mac. SwiftUI takes a similar approach to UIKit in Catalyst optimized for Mac, but you tend to get more for free. Its declarative syntax and layout allows it to do more for your views when optimizing them for the Mac.
If you've worked with SwiftUI, you've seen how it already adjusts your views on a context-aware basis. For example, changing a button's style if it's in a form or a menu. SwiftUI also knows if views are being used in the Mac idiom. Just like your controls change their appearance for where they are in an iPadOS view, so, too, will they represent themselves differently optimized for Mac.
You can still expect to do some layout work to optimize SwiftUI interfaces for the Mac. Your SwiftUI layouts will by and large shift based off the containers you use to compose your views. As always, you can build more flexible and reusable layouts, keeping your views modular and making proper use of containers. Let's take a whirlwind tour through some SwiftUI controls and how their appearances change from "scaled to match the iPad" to "optimized for Mac." This year, GroupBox is new on iOS and iPadOS. GroupBox is used for layering structured content and automatically receiving the right semantic colors as backgrounds for that content. In this code sample, I've used a GroupBox in a VStack nested in another GroupBox, and the views have rendered automatically with different background colors. Your SwiftUI GroupBoxes will appear as Mac GroupBoxes when optimized for Mac. This comes with some automatic padding and layout adjustments.
Here, I've set up a Toggle in code that uses the variable completed as its Bool binding. You'll see that the default representation of a Toggle in SwiftUI on iPadOS is the sliding switch.
We've already seen that Mac Catalyst and UIKit make the Mac checkboxes available, and so does SwiftUI. In Catalyst Optimized for Mac, the default Toggle appearance is a checkbox. You can use the ToggleStyle structs and ToggleStyle modifier to specify a style if the default isn't right for your case.
Similar to UIKit, system iOS buttons will adopt the native Mac appearance. You can even place SF Symbols alongside them.
These are both definitely fantastic buttons. I don't know about you, but one of these looks more clickable to me.
DatePicker is a little different. On the right, you see the DatePicker in both idioms of Catalyst. The DefaultDatePickerStyle renders identically across Catalyst. Remember that here, too, there are, of course, other styles available to you. The DefaultDatePickerStyle is compact in both idioms.
This content view sets up a picker to pick from the small, medium and large sizes. Its binding is set up through the property sizeIndex. You could also bind via string values or an enum.
When you optimize for Mac, the Mac picker becomes available to you. This is a control users are very familiar with on the Mac, and offers a much more Mac-like experience than the scrolling picker. Remember, the segmentedControlStyle is an option on both platforms too.
One last control to mention. You know that the system buttons are available to you, however, you also aren't sacrificing any customizability on your SwiftUI buttons. If you aren't using the system button appearance, then you're using some custom body. Hopefully, you're using SwiftUI's ButtonStyle API as I've done here, to make your button styles flexible and reusable.
SwiftUI and Catalyst know not to render custom buttons with the Mac appearance, so your custom buttons come across exactly as they are.
I've taken some time to write a fully custom throwback NinetiesButtonStyle. You can see I've applied it as a view modifier to the button in my ContentView's body. Thanks to Catalyst, I can have this on iOS and Mac without needing to write any additional code.
So I, like some of you, maybe, have been experimenting with SwiftUI. And you may know that SwiftUI can run seamlessly in UIKit apps. It's really easy to build up a content view in the SwiftUI previews canvas, and then drop that right into my UIKit code with the UIHostingController.
I thought that this text-only view from the Cookbook app Jake was showing earlier could use a makeover. So I made this interactive recipe view completely with SwiftUI. It lets the user check off their progress as they work through the recipe.
You can select which units of measurement to use with a picker at the top.
Each instruction is contained in a GroupBox...
and can be marked complete with a Toggle.
I've used a custom blue timer view and custom SwiftUI button to control that timer. Now let's see how this new iPad feature work carries over to our Catalyst app optimized for Mac. Here's the app with the plain text directions view. And here it is with my iPad SwiftUI view dropped right in, no code changes at all. I am really happy with these changes.
The picker has adopted the default Mac appearance. The iOS style GroupBoxes are now using the Mac style. Notice how using the GroupBox also achieved a more Mac-like layout that I otherwise would have had to do myself.
The Mac GroupBox uses Mac metrics, so it's a little bit more snug around the views, and the label of the GroupBox has moved outside the box which also affects what completed instructions look like.
The sliding switches are all now checkboxes, and the labels of those checkboxes are to the trailing edge of the box. Those labels are also clickable to toggle the checkbox, and this is different from a sliding switch's label behavior.
And my custom views are untouched and scaled correctly.
I'm very excited with how easily I was able to bring SwiftUI right into this app and make following our recipes easier and more interactive on the Mac and the iPad. Let's review all that we've learned today. You saw what you to expect to change when you optimize your Catalyst interfaces. We went over traits that might make an app a good candidate for optimizing for the Mac. Jake showed you how to optimize your app in Xcode using Interface Builder's trait variations and idiom checks in code.
Finally, we saw how SwiftUI optimizes for Mac, and how easy it can be to integrate new SwiftUI views into your UIKit app. Thanks for watching. Enjoy WWDC 2020.
-
-
22:04 - Hide Navigation Bar in Mac Idiom
if traitCollection.userInterfaceIdiom == .mac { navigationController?.setNavigationBarHidden(true, animated: false) }
-
29:33 - Idiom vs conditional compilation block
// Idiom vs conditional compilation block if traitCollection.userInterfaceIdiom == .mac { // "Optimize Interface for Mac" specific code } #if targetEnvironment(macCatalyst) // Mac Catalyst specific code #endif if traitCollection.userInterfaceIdiom == .mac { // "Optimize Interface for Mac" specific code } else if traitCollection.userInterfaceIdiom == .pad { #if targetEnvironment(macCatalyst) // Mac Catalyst specific code #else // iPad specific code #endif }
-
31:26 - SwiftUI GroupBox
// Nested GroupBoxes struct ContentView: View { var body: some View { GroupBox { VStack { Text("High level information") GroupBox { Text("Some elaborate details") } } } } }
-
32:00 - SwiftUI Toggle
// DefaultToggleStyle struct ContentView: View { @State var completed: Bool = false var body: some View { Toggle("Complete?", isOn: $completed) } }
-
32:35 - SwiftUI Button
// System Button with SF Symbol struct ContentView: View { var body: some View { Button(action: { }, label: { HStack { Image(systemName: "rays") Text("Click Me!") } }) } }
-
32:56 - SwiftUI DatePicker
// DefaultDatePickerStyle struct ContentView: View { @State var dueDate = Date() var body: some View { DatePicker("Due:", selection: $dueDate) } }
-
33:14 - SwiftUI Picker
// DefaultPickerStyle struct ContentView: View { @State var sizeIndex = 2 var body: some View { Picker("Size:", selection: $sizeIndex) { Text("Small").tag(1) Text("Medium").tag(2) Text("Large").tag(3) } } }
-
33:55 - SwiftUI Nineties Style Button
// Custom gradient button struct CustomNinetiesButtonStyle: ButtonStyle { var angle: Angle = .degrees(54.95) func gradient(shifted: Bool) -> AngularGradient { let lightTeal = Color(#colorLiteral(red: 0.2785285413, green: 0.9299042821, blue: 0.9448828101, alpha: 1)) let yellow = Color(#colorLiteral(red: 0.9300076365, green: 0.8226149678, blue: 0.59575665, alpha: 1)) let pink = Color(#colorLiteral(red: 0.9437599778, green: 0.3392140865, blue: 0.8994731307, alpha: 1)) let purple = Color(#colorLiteral(red: 0.5234025717, green: 0.3247769475, blue: 0.9921132922, alpha: 1)) let softBlue = Color(#colorLiteral(red: 0.137432307, green: 0.5998355746, blue: 0.9898411632, alpha: 1)) let gradient = Gradient(stops: [.init(color:lightTeal, location: 0.2), .init(color: softBlue, location: 0.4), .init(color: purple, location: 0.6), .init(color: pink, location: 0.8), .init(color: yellow, location: 1.0)]) return AngularGradient(gradient: gradient, center: .init(x: 0.25, y: 0.55), angle: shifted ? angle : .zero) } func makeBody(configuration: ButtonStyleConfiguration) -> some View { let background = NinetiesBackground(isPressed: configuration.isPressed, pressedGradient: gradient(shifted: false), unpressedGradient: gradient(shifted: true)) return configuration.label .foregroundColor(configuration.isPressed ? Color.pink : Color.white) .modifier(background) } struct NinetiesBackground: ViewModifier { let isPressed: Bool let pressedGradient: AngularGradient let unpressedGradient: AngularGradient func body(content: Content) -> some View { let foreground = content .padding(.horizontal, 24) .padding(.vertical, 14) .foregroundColor(.white) return foreground .background(Capsule().fill(isPressed ? pressedGradient : unpressedGradient)) } } } struct ContentView: View { var body: some View { Button("Awesome", action: {}) .buttonStyle(CustomNinetiesButtonStyle()) } }
-
-
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.