Xcode 11 introduces new features for finding and fixing bugs fast. Discover how to simulate network conditions and thermal states, and how to override your app's runtime environment while debugging. See how the debugging features work with Xcode previews to identify issues before Build & Run. Learn how to work with the View Debugger to troubleshoot your SwiftUI views.
Hello, and welcome to debugging in Xcode 11. I'm Chris Miles, one of the engineering managers on the Xcode team. I'm really excited to be here today. We've got a lot of great stuff to get through, so I'm going to jump straight in and start by telling you about device conditions and environment overrides.
So, you may have received reports from users where your app isn't behaving as expected under certain real-world conditions, and they may be the types of conditions that are really hard to replicate in your development environment. Such as real-world networking conditions. Network response out in the real world isn't always as ideal as it is in our home or office where we typically build our apps. And if users report an issue while using your app in situations where network connectivity is limited, you need a way to reliably reproduce these types of network conditions to debug and improve your app. Or, when devices out in the real world are under thermal pressure and need to regulate resources available to your app in order to help cool down the device. You need to understand how your app behaves in these types of thermal conditions and for apps that make heavy use of resources, you may be proactively responding to thermal state changes by reducing resource usage when the device is under thermal pressure. But how do you reliably put the device into an elevated thermal state in order to debug and verify your logic? And this is where device conditions come in. New in Xcode 11, you're able to use the new thermal state condition to safely and reliably raise the thermal state of a device to any of the elevated levels, fair, serious, or critical. You can use this to understand your app's behavior under these elevated thermal states, and to debug and verify your handling of thermal state changes.
Just note the device does not actually get physically warmer, so don't use this functionality to try and warm your hands on a cold day. Along with the thermal state condition, we also have the network link condition. This allows you to simulate real-world networking conditions directly from Xcode on that device, such as a high latency, low bandwidth, high packet loss, and slow DNS. So you can use this to understand and debug your app's behavior under these various networking conditions. Now this functionality is similar to the network link conditioner in iOS developer settings that some of you may be already familiar with, but it has been updated with a wider range of modern network profiles, and it's fully controllable by Xcode, and I'll show you how that works in a few moments. Now, some conditions that an app must adapt to are controlled by the user. There are numerous settings that a user can adjust in order to customize their interface, such as the light or dark interface style that an app is running in. In iOS 13, your app must provide a great user experience when running in either light or dark. A setting that is highly customizable is dynamic type.
Users can choose between very small dynamic type sizes up to very large sizes, and there are even larger sizes available in the accessibility settings. So you need to be able to debug your UI layout across a wide range of dynamic type settings. And then there are many accessibility options available as well. Many system controls already adapt to these settings, but you need to ensure that your custom views and controls also do the right thing when these settings are enabled. As you can see here, there's a large matrix of environment settings available to users. Do you debug and test your app with many of these settings adjusted or enabled? Do you even remember where to find many of these settings? And this is where environment overrides can really help, allowing you to effortlessly override any combination of these settings while debugging your app directly from Xcode. That's a quick introduction into device conditions and environment overrides, but I'd love to show you how they work, and I'm going to do that by jumping into Xcode.
So here we have Xcode open with the Trouble Project. This is an iOS app that we've been building that allows users to discover places to travel to, create a travel plan, and record a travel journey. You may have seen it from some of our other sessions this week. I've been focusing on this beautiful 3D rendering of the Earth here with pins in it indicating places to travel to.
Now, I want this app to be a good thermal citizen, and so I've started adding code to observe thermal state changes on the device, and I'd like to reduce the use of GPU resources when the device is under thermal pressure. So, let me show you how I've done that. I'm going to switch to this Globe Scene Controller. This is the controller responsible for configuring the 3D scene.
In the initializer, you can see that I've added an observer for the thermal state did change notification. This is called whenever the thermal state of the device changes. In the callback, we call through to configure scene features. So let's take a look at this function. It's responsible for two parameters of our 3D scene, the anti-aliasing mode and the requested frame rate.
So when this is called, we can fetch the thermal state of the device, and then when the device is in the critical thermal state, the recommendation is to reduce our use of energy impactful resources as much as possible to help the device cool down. So, in our case, what I'm going to do is to turn off anti-aliasing and also to reduce our frame rate to 30.
In the serious thermal state, we don't have to be as aggressive, so let's just turn off anti-aliasing but leave the frame rate as it is, and in the lower thermal states, we don't have to take any action in this particular case. So with that change in place, I'm going to stop and rerun, and I have a device next to me, and I'm mirroring that device back to the desktop using QuickTime player on the right.
Now what I'd like to do is to put the device into the critical thermal state, so we can test this logic and debug it if there are any problems, and so I can use the new device conditions for that. But before I do that, I'd also like to monitor the thermal state of this device, and I can do that now by switching to the debug navigator and selecting the energy impact gauge. This gauge shows you a breakdown of the average energy impact as well as the component utilization here on the top right, and you can see as I interact with the app the GPU is the primary component in this case, as expected. But I'm going to draw your attention to the middle here where we have the new thermal state track available in Xcode 11. This shows you two pieces of information. The top of the track shows you whether any device conditions are active, and there are no device conditions active yet. The bottom part shows you the current thermal state of the device. And blue indicates the nominal thermal state, everything is running cool and smoothly. So let's leave that open while I use the window menu to open the devices window.
I'll move that up here. Now, with the device selected, we can scroll to the bottom and find the new device conditions UI.
And here we can see all the device conditions available for this device. We have the network link condition and all the new profiles available. In our case, let's select thermal state. And here we can choose an elevated thermal state level, and I'm going to choose critical and press start. Now, there's a few things to take note of here. One is that it takes a few moments for the device to transition to the critical thermal state, and down here in the thermal state track, we see confirmation that that device condition is now active, and after a few moments, you can see that the device transitioned to the critical thermal state. Over here on the right, on the device itself, note in the top there's a background filler on the time, and that's you status bar indicator that a device condition is active on this device. Now, finally, if we interact with our 3D scene, we can see that the frame rate indicator that we've added shows 30 frames per second. And so we can verify easily as that, that our logic is doing the right thing. Also, the anti-aliasing is switched off. It's a little hard to see on this small device, but on a larger device, you'd be able to see that. So now that we've used device conditions to verify our code, we can switch it off. I could press the stop button here, but there are a couple of other ways to disable the device condition. One, is to disconnect that device from Xcode or quit Xcode. If the device ever comes disconnected from Xcode, it will automatically tear down a device condition, because we don't want to leave your device running in one of these states when you're not developing. Another is on the device itself, we can tap that status bar indicator at the top, which I'll do now, and we'll see information about the currently active condition, and I can press the stop button to turn off that condition. So, I'll press that now. We can close the devices window, and in the thermal state track here, we see confirmation that the device condition is no longer active, and we can see that the device starts transitioning back down to the fair thermal state and the nominal thermal state. So that's a quick example of using the device conditions, in this case the thermal state condition on our connected device. Now, I'd like to move on and show you environment overrides. Now what I'm going to do is stop running and switch to a particular view. I'm going to choose profile view. This is written in Swift UI, and I really, really like writing Swift UI and using the new Xcode preview. So I'm going to open the canvas to start the preview engine.
Now, I don't need the device anymore, so I'll close QuickTime.
Previews will start up. Now, to use environment overrides, you need to be debugging your app or debugging your preview, and if you haven't seen already, you can debug previews by control clicking on the live button down on the bottom right. You'll see a pop-up menu, and you can just choose debug preview.
This does two things. It puts the preview into live mode, and it also attaches a debug session. So, now the preview is live and interactive, and here we go down the bottom, which I'll lift up a bit. In the debug bar, we can see that we have confirmation that we have a debug session active debugging the preview. So we have all of our usual debugging tools, such as view debugging, memory graft debugging, and the new environment overrides. So, if I click that, we see the environment overrides UI, and we can simply override the interface style by clicking the switch, and now we've put that preview into dark mode. We can just switch back and forth between light and dark to test our layout and test our UI.
So, you might have already noticed an issue. The description text here looks great in light, but in dark, we can't even see it. So, while we're here, let's take a quick look.
This text element under my cursor is responsible for drawing the description, and you can see that I haven't finished transitioning this view to take advantage of dark mode. I'm still using a fixed color. I should use a semantic color like secondary, so I'll make that change. What I love about previews, refreshes for me. Because we have asked for debugging, we still have a debug session, so I can bring up environment overrides and look at dark mode, and we've made our fix as easy as that. Let's try out the dynamic type override. I'll switch on text overrides. I can simply use this slider to choose any of the dynamic type settings down to the very smallest, up to the very largest, and then we can keep going into the accessibility sizes, all the way up to the very largest accessibility dynamic type size. Notice that our layout is pretty good in this case except here. These little rectangles get clicked in the very largest type sizes. So, while we're here, let's fix that too. I'm going to scroll down to image text pill is the view responsible for drawing these rectangles. It lays out an image and a text in a horizontal stack.
What I'd like us to do is when the type size gets too large, to switch that to a vertical stack and lay them out one above the other. Now, I've had to solve this elsewhere, and I build a custom view called an adapting stack. I'm going to option click that now to open that, and we don't need the canvas in this case, so I'll close that. So we've opened this in an editor split, and let's take a quick look. An adapting stack is a custom view that takes a view builder closer as an import, the same as an H stack or a V stack does.
It binds itself to the size category environment. And so when a size category changes, which is what happens when you change dynamic type, our bodies fold, and then if we switch on size category, if it's accessibility large or larger, then we wrap the content in a vertical stack and adjust some padding. Otherwise, we wrap it in a horizontal stack. So, let's try this one out.
I'll change this H stack to an adapting stack. Our preview will refresh for us, and now we can try out our environment overrides. And now, as we go into the large sizes, we can see that our layout adapts. I'm happy with that change, and let's take a quick look at accessibility settings. You can see there are a lot of settings here. We don't have time to play with them today, but I highly encourage you to try out all these settings with your apps and previews and make sure your UI is doing the right thing with these enabled.
So let me give you just a quick summary of what I showed you. First, we looked at device conditions, which is available from the devices window. It allows you to enable a network link condition or a thermal state condition.
Device conditions are tied to the Xcode device connection. So if the device becomes disconnected from Xcode, we automatically tear down that device condition. And in iOS status bar indicator, it is your indication that a device condition is active on that device. You can tap that indicator to see details about the condition and use it to stop the condition.
Environment override is available while debugging. You can use to instantly override many of the settings that you saw, and it only affects your debug app. It does not affect any other processes that are running, and it doesn't, importantly, it doesn't change any system settings. So, it's really great and convenient to use. It's supported while debugging in any cases, devices, simulators, or previews as you saw, and it's available for all of our platforms. Some of the settings may differ, but it's available for any of the platforms that you can debug. So, I hope you can take advantage of these new features to develop and test your apps in a wider range of conditions and environments so you can create a great user experience for more users in more situations. Thank you, and I'm going to hand over to Han Ming, who is going to tell you more details about debugging live previews.
Thank you, Chris.
Good morning. In the past few days we have learned that live previews are great for testing interactions between, with a particular view of your project, and Xcode is optimized for the fast turnaround time between editing your source code and updating the preview.
But because some of us, no all of us are mere mortals, we are bound to introduce code, a bug in the code.
So Xcode allows the debugger to be attached to the preview instance in order to seek out those bugs. When the debugger is attached, you access to all the great tools, such as debug navigator where you can examine the backtrace. Break points, where you can pause the debugger. Debug bar, where you can search for your process control button, such as stepping controls.
And variables view, where you can inspect the state of the process when paused.
New in Xcode 11 is going to start showing you Swift UI runtime issues. Now this is part of a new category of issues that are generated by system frameworks, and typically it's related to how their APIs are not used correctly.
Xcode will find them when the process is running, and we currently have a couple of frameworks that are participating, and because Swift UI is brand new, it's designed from the get-go to generate these issues for you.
These issues do not crash a process, but instead they point to important bugs that we want you to solve before deploying your app to the app store. To grab your attention, they will show up prominently in Xcode. They will show up as purple icons in the activity viewer and with more details in the issues navigator.
Besides previews, these issues are available for all platforms and run destinations.
To learn more, let's get straight to demo. All right. This is the project that we have been working on, and the canvas starts off with showing you the static preview.
And this preview will show temperature on the right-hand side when data has been fetched.
But before that, it you will show the spinner. The presence of the spinner is controlled by a state value called loading.
Loading becomes false when data has been fetched, and that's when with the spinner disappears.
Pretty simple logic.
So to get the live preview, as Chris mentioned, you click on the play button.
To get the debugger attached, you can control click to bring up the contextual menu and select debug preview.
Now, my QA engineer told me that she has found a bug in which spinner doesn't disappear. So keep an eye, keep your eyes on it while I get the debug session going. What happens now is that Xcode will recompile this particular Swift file with debug info and launch the new preview.
You know that the debugger is attached in two ways.
First, you can go to the debug navigator and look for it, or you can look for the process control buttons in the debug bar.
It looks like we have a debug session going, and in the canvas, we can see that the spinner didn't disappear. So she's right.
Let's solve this bug.
Under the debugger, we can see that a new runtime issue is showing up in the editor. We can click on the annotation, and it will show you a message. Says here modifying state during view update.
This will cause undefined behavior. Now that's quite a mouthful, so let me explain that.
The body property retains a view hierarchy that will be shown later.
The framework will ask for the body property whenever it thinks that the views need to be updated. So you have no control over when or how many times it's going to be asked.
Thus there's no context, there's absolutely no context for you to make a decision on how to change the state value.
Luckily for us, its loading can be changed at a better place, which is at the callback of the weather fetcher.
On line 47. So, I'm going to move this chunk of code to line 47, but you will see that Xcode notices source code added changes and will automatically try to launch a new preview. So, again, keep your eye on the spinner in case I'm doing it too slow.
So here's the spinner.
Data is fetched.
Oh wait, debugger session has to come in.
Here's the spinner. It's fetching the data.
The data comes in, spinner disappears. So bug is number one solved. To stop the debug session, you go back to the same place where you started it and click on the stop button.
So my ever diligent QA engineer told me that she also has a different bug for me, and this time it's with a different view. It's called a plain details view. So let me show it.
And I'll get the debug session going while I describe the situation.
So we have a header here at the top, and the design here is for it to get slightly bigger, when I do a pull down on the screen. This is to help folks like me see better. So, it looks like we have a debug session, so let me try it out.
So it does get bigger, that's good. And at some point it suddenly disappears. So this must be the bug she's referring to.
And then over here I'd noticed that a purple icon popped up in the activity viewer.
So, I'm going to click on that, and that should bring me to the issues navigator where it says cannot invert singular matrix. Hmm. For those of us who breathe CGAffine transform daily, you know exactly what this means.
For the rest of us, here's a two-hour lecture on high school linear algebra.
And there's my math PhD friend pumping his fist, but no, let's not do that. But here's a very brief explanation.
Getting the inverse of a matrix is akin to doing some math division. Now math division you guys all know. There are some values that are simply not possible.
So, we'll see why in a little bit, but for now let me draw your attention to another piece of data in the issues navigator, which is the recorder backtrace generated by the debugger.
You disclose that, you will see backtrace and then there will be a frame with your debug symbol, which is frame number one. You select that, it will navigate you to the following line where the issue is generated. But in this case, knowing where is less important than how or when.
And typically to do that, we need to pause in the debugger, and then you can inspect the state of the process. Now you guys probably know what tool I'm going to use to hunt down these bugs.
Yes, break points.
I can simply put a final break point in line 44, but guess what? It's going to be hit many times when I do the pull down, so it's going to be less effective. So, let's not do that.
Instead, let's head over to the break points navigator and click on the add button. Where in Xcode 11 we have introduced a new breakpoint called runtime issue breakpoint, let's add that.
We'll leave the target system frameworks.
Now, during the demo I like to introduce tips and tricks that will make your workflow more productive. Here's another good one. If you ever have a need to stop and restart your preview, you can use the appropriately named refresh shortcut under the editor menu. Let's try that.
So we have a new preview with a debugger attached. The Xcode will always remember your previous configuration. So if you use the debugger, the debugger will be attached to the new preview session.
So, I'm going to do a pulldown. If I'm lucky, I'll hit the breakpoint. There we go. And Xcode conveniently brings up the variables view. Now we know there's something wrong with our skill transform, so I'm going to twist it open and look at the matrix.
M11 is the value that we're using for X scaling. We're not doing anything with it, so we're keeping it at a constant one.
M22 is the value that we are using for Y scaling, and when this value gets to 0, the math formula for getting the inverse just doesn't work, right. Yeah. So, now we know why the issue is generated, and let's get up in the code to see where I assign the value to be 0, for the Y scaling to be 0. It looks like it's this line right here. Now fix it. And while I'm here, I'll make the code more succinct.
All right. I will close the debug area.
All right, let me try it now and get-- okay, it gets bigger, and it sticks. Good. Bug number two solved, and that actually marks the end of my demo. So, when you are debugging live previews, there are three things that can be different compared to when you are doing regular debugging. First, you use the contextual manual of the play button in the canvas to start you debug session. To stop it, you click on the same button. And you guys saw that Xcode automatically notices source code added changes and then tries to launch a preview, a new preview for you. This happens with or without the debugger. But with the debugger, there will be a new debug session. So, if you want to keep the same debug session associated with the same file in the editor, do not change that file. To view a different file, you use a different pane, a different tab, or a different window. So Sebastian will show the workflow in a little bit.
All right. For my demo, we learned that when you are debugging live previews, you have access to all the great tools that you are so used to, but to be more productive, you want to leverage on the workflows that we have designed especially for previews.
And pay attention to those Swift UI runtime issues, because they do point to real bugs and make use of the runtime issue breakpoint to help you with the debugging. And I've shown you several ways to do debugging, but sometimes you just need to do it visually, and to do that, let's bring out Sebastian.
Thank you Han Ming.
We will indeed take a look at debugging Swift UI View hierarchies in a demo, but before we do so, let's take a look at a couple characteristics of Swift UI and key differences between Swift UI and traditional UI frameworks like AppKit and UIKit and see how that may impact your debugging workflows.
First of all, Swift UI makes use of a declarative API. That means, instead of initializing the views that will be presented at runtime yourself and your own source code, you provide a recipe of your view hierarchy, and the framework constructs your views and keeps them up to date for you at runtime.
Swift UI embraces composition and lightweight modifiers over complex view types, so views that you may be inspecting at runtime have a lot less properties to inspect.
Views in Swift UI are value types. That means debugging workflows that rely on references to views may not be applicable anymore, and at runtimes, Swift UI may translate certain views that you use in your code and to platform specific UIKit and AppKit views, and we will see an example of that later in the demo. Swift UI also mixes very well with existing framework views from your UIKit or AppKit, and the travel app that you saw earlier actually makes use of exactly that concept to mix a UIKit view with the Swift UI views, which are the main views used in that application.
With these aspects in mind, let's switch to the demo machine and take a look at inspecting the view hierarchy of our profile screen of the travel app.
And we're debugging the profile view of the travel app again, and we're already in debug mode here. And let me resize the inspector a little bit.
To inspect the view hierarchy, we can use this button down here in Xcode's debug bar to debug view hierarchy. And as Han Ming mentioned earlier, we have to ensure that the preview stays open while we're debugging since the debug session is tied to the lifetime of the preview. We can do so using the new editor split feature in Xcode 11 by holding down option while we click the debug view hierarchy button in Xcode's debug bar. That will open up the view debug canvas in a separate editor split, and we have access to the source code on the top left, the preview on the bottom left, and the view debug canvas on the right-hand side.
If you've used the view debugger before, this is very familiar to you. You have a canvas areas where you can inspect all your views. You can export in 3D by simply rotating, so you get a good overview of your views, and you can select elements and see inspect the properties on the right-hand side.
The navigator on the left-hand side has an outline representation of your view hierarchy so you can orient yourself relative to subviews and superviews.
Okay. So, as I mentioned earlier, this profile view combines UIKit and Swift UI, and looking at the implementation, you can see that it returns a vertical stack as part of its body, which includes avatar view controller view here, and that view simply loads a UI view control from a storyboard file, and below that, it instantiates a list.
What that means, the top part here is built up using UIKit, and the bottom part is built up using a list with Swift UI.
The first part of this demo, I want to focus on the UI Kit part of the view hierarchy, and then the second part will focus on the Swift UI part.
New in Xcode 11 is support for UI Window Scene, which is a concept used for multiwindow support and UIKit apps in iOS 13, and you can see how all UI windows are now listed below owning windows scene. So once you add multiwindow support in your iOS app, it's very handy to look out for these window scenes, and when you select one in the navigator, you can also inspect their properties, for example, see the title and the activation state in the inspector.
Another new feature in iOS 13 is dark mode, and there are several ways where we enhance the view debugger this year to help you add support in your own application.
First of all, when you select a UI view or UI view controller, we now show you trade collection information in the inspector, such as the interface style, so you can ensure that the interface style is as expected, and when you override the interface style for certain subparts of your view hierarchy, you can debug it right from the view debugger.
Another key feature of dark mode are dynamic colors that adapt to appearance changes, and a good way to do that is using asset catalog and name colors. And when we select this badge here, you can see that it's achieving its orange tint with a named color, the accent color, which is coming from an accent catalog, so it's now very easy to tell whether a color is a name color coming from an accent catalog that can dynamically adapt to appearance changes or whether it's a hard-coded color that cannot adapt to appearance changes.
Another new feature are symbol images, and this badge actually makes use of a symbol image. When I just go up a little bit in this inspector, you can see details about the symbol image in the inspector. We now show names for all images that you select in the inspector, and you can get details like symbol configuration and see that it's in fact a system symbol image compared to for example a custom symbol that you created in your assent catalog.
Symbol images are aware of their baseline, so they lay out very nicely next to text. We visualize the baseline in this view on the right-hand side, and to make sure it actually lays out nicely next to text, we have to, in the UIKit world where we use auto layout constraints and ensure that we use the baseline constraints instead of, for example, centering them. Because if this name gets longer and causes a line break, and we have a multiline label, we want to ensure the badge is aligned to the first line of this label.
We can very easily verify this with the badge selected. We can switch to the size inspector and take a look at the constraint details. We overhauled this section in Xcode 11 to be a lot more like interface builder and be more powerful, so you can now highlight constraints as you mouse over them. You can use this mini navigator here to filter down to a specific edge, and we can easily confirm that this badge is in fact using the first baseline constraint to the label that's next to it. So, that's exactly what we want, and we were easily able to confirm this.
Okay. So, these are the enhancements we made on the UIKit side. Let's switch our focus to SwiftUI.
As we saw, we're currently looking at the preview of the profile view, and looking at the navigator on the left-hand side, you can see the profile view shows up there, and we can get a good understanding of its size in the canvas area.
We saw that it's using a vertical stack, and we can also see that in the canvas area, and selecting it, you can get details about it and then inspect on the right.
Switching back to the profile view, which has a few more properties, you can see that a lot of properties about this view show up in inspector, and we actually use Swift reflection to automatically discover these properties. So, even for your own custom views, we automatically now show these properties in inspector, which is a lot more powerful than before where we only supported system views. And we even went one step further. You can now customize the properties that show up in inspector on the right-hand side by implementing the customer reflectable protocol on your view types, and profile view is exactly doing that. You can see that it's returning three different properties here, account status, last login, and profile, as part of a custom mirror, and looking at the inspect on the right, you can see that these are exactly the properties that I'll return. So that's a very powerful feature to let you return exactly the data that you need while you're debugging.
Taking another look at the inspector, you can see that at the top we get properties of view that is selected currently, the profile view, and then further down, we can see all the modifiers that are currently in effect on that selected view. So we can see details about the padding and the safe area inset, for example.
Let's take another look at a different part of the view hierarchy, and as we saw earlier, the bottom part is using a list to lay out all the views.
So let's select one of the views that we see here, for example, the 965 text here, and I will use a gesture of navigate and reveal in debug navigator so we have a good understanding of where it is inside the view hierarchy.
And we can see that Swift UI actually translated that list into a UI table view under the hood, and it's using table view cells to host the Swift UI views that we construct in our code.
This is an implementation detail, and you should not rely on it in your implementation, but it can be very helpful when you're trying to debug layout issues, for example. With this text selected, there's actually an issue here that our designer pointed out. Instead of vertically centering these two texts, they should actually be baseline aligned. Now, we already saw that there's a horizontal stack that is laying them out next to each other, and when we take a look at the implementation, here, you can see that it's a plain horizontal stack that has no parameters passed to its initializer. But when we take a look at the inspector, you can see that it actually defaults to an alignment of center. Now, we don't want center alignment, we want the baseline alignment.
So, let's adjust the code and fix this problem. I will close the view debugger, continue the debug session, and activate the previews again.
And for the H stack, I will now add a parameter of alignment to dot first baseline, and we can now see that the two labels are in fact baseline aligned. So we were-- We were able to see the default value that the H stack assumed for its alignment, figure out the fix for it, and then instantly verify it using the new previews in Xcode 11.
With that, I will switch back to slides.
We just saw, when you're inspecting Swift UI view hierarchies, the view hierarchy shows up in the navigator on the left-hand side and in the canvas area, and properties and modifiers are presented in inspect on the right-hand side.
Inspect the properties are automatically discovered using Swift Reflection, and you can even customize the inspector properties that show up in inspector on the right-hand side using the custom reflectable protocol.
And, of course, just as Swift UI, we also have support for mixed view hierarchies, and we just saw how the UIKit view showed up right next to the Swift UI views.
We also have numerous enhancements for UIKit views, namely support for UI Window Scene, which will be handy as you start adopting multiple windows in your UIKit applications, and we have inspector enhancement to provide even more details such as trait collection details. We saw the named images and symbol image information. We also saw named colors, which will be very helpful as you start it up in dark mode. And we have greatly improved constraint details in the inspector, which are more powerful than ever before when you're debugging your auto layout constraints in your UIKit application.
If you want to learn more about device conditions, I recommend this session here, and there's also a great video that's teaching about LLDB and enhancements.
With that, that brings us to the end of this talk.
I wish you a fantastic rest of the conference, and I'll see you at the bash. [ Applause ]
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.