Streaming is available in most browsers,
and in the Developer app.
-
Unleash the UIKit trait system
Discover powerful enhancements to the trait system in UIKit. Learn how you can define custom traits to add your own data to UITraitCollection, modify the data propagated to view controllers and views with trait override APIs, and adopt APIs to improve flexibility and performance. We'll also show you how to bridge UIKit traits with SwiftUI environment keys to seamlessly access data from both UIKit and SwiftUI components in your app.
Chapters
- 0:57 - Understanding traits
- 7:44 - Defining custom traits
- 14:31 - Applying overrides
- 19:48 - Handling changes
- 25:24 - SwiftUI bridging
Resources
Related Videos
WWDC23
-
Download
♪ ♪ Tyler: Welcome to "Unleash the UIKit Trait System." I’m Tyler Fox, a UI frameworks engineer, and I’m excited to tell you about some incredible new UIKit features that you can take advantage of in iOS 17. To start, I’ll review fundamentals about the trait system in UIKit. Then I’ll guide you through the new features and capabilities, which include the ability to define custom traits to add your own data to UITraitCollection, easier ways to apply trait overrides in your app’s hierarchy, and more flexible ways to handle when traits change. Finally, I’ll explain how you can bridge UIKit traits with SwiftUI environment keys to seamlessly pass data between UIKit and SwiftUI components in your app. It's time to review some fundamentals. Traits are independent pieces of data that the system automatically propagates to every view controller and view in your app. UIKit provides many built-in system traits, such as user interface style, horizontal size class, and preferred content size category. In iOS 17, you can define your own custom traits as well. This unlocks a powerful new way for you to provide data to your app's view controllers and views. I'll tell you all about custom traits a little bit later. The main way that you work with traits in UIKit is using trait collections. A trait collection contains traits and their associated values. There are some new APIs in iOS 17 that make it easier to work with trait collections. First, there is a new initializer that takes a closure. Inside that closure, you receive a mutable traits container that you can set values to. This mutable container conforms to a new protocol named UIMutableTraits. Inside the closure, I set the user interface idiom to phone and the horizontal size class to regular. When the closure finishes executing, the initializer returns an immutable UITraitCollection instance that contains all of the trait values I set inside the closure. There’s also a new modifyingTraits method that allows you to create a new instance by modifying values from the original trait collection inside the closure. Here I’ve changed the horizontal size class to compact, and I've populated a dark value for the user interface style. Since I haven't changed the user interface idiom, it remains equal to phone from my original trait collection. While you can create your own trait collections like this, most of the time you obtain trait collections from trait environments. Trait environments in your app are window scenes, windows, presentation controllers, view controllers, and views. Every one of these trait environments has its own trait collection, and each trait collection may contain different values. Trait environments are connected in the trait hierarchy, which is how traits flow through your app. Here's an example of the trait hierarchy’s tree structure, from each window scene all the way down to the individual view controllers and views. Each trait environment inherits trait values from its parent environment. Always use the trait collection of the most specific trait environment possible. I'll dive deeper into the way that traits flow through view controllers and views.
Here is an example of a parent view controller, which contains a child view controller. The dashed line represents the view controller hierarchy. The parent controller owns a view, and the solid line connecting them represents their relationship. The parent view has one subview, and the dashed line through the views represents the view hierarchy. Finally, the view of the child controller is a subview of that middle view. First I’ll explain how traits flowed through view controllers and views prior to iOS 17. View controllers inherited traits directly from their parent view controller. And views owned by a view controller inherited their traits directly from their view controller. Finally, views without a view controller inherited traits directly from their superview. This behavior meant that the flow of traits in the view hierarchy stopped at each view owned by a view controller. For example, a trait value from the view of the parent controller would only be inherited by its direct subview. The view of the child controller wouldn’t receive that value, even though it is a subview in the view hierarchy below those views. This behavior could be surprising. In iOS 17, we’ve eliminated this issue by unifying the trait hierarchy for view controllers and views. View controllers now inherit their trait collection from their view’s superview, instead of directly from their parent view controller. This creates a simple linear flow of traits through view controllers and views. Note how view controllers still inherit traits from their parent view controller, it just happens indirectly via the views in between them. Because view controllers now inherit their traits from the view hierarchy, a view controller’s view must be in the hierarchy for the view controller to receive updated traits. As a result, if you access a view controller’s trait collection before its view has been added to the hierarchy, the view controller won’t have up-to-date values for its traits. The most common place you might find code that is affected is inside viewWillAppear, as that is always called before the view has been added to the hierarchy. There's a new callback named viewIsAppearing that you can use instead. viewIsAppearing is called after viewWillAppear once the view has been added to the hierarchy and both the view controller and view have up-to-date trait collections. viewIsAppearing is a drop-in replacement for nearly all cases where you’re using viewWillAppear today. And the best part is that this new method back-deploys all the way to iOS 13. Watch "What’s New In UIKit" to learn more about this new callback and how it fits into the view controller life cycle. iOS 17 also improves the consistency and performance for view trait updates. Views only update their trait collection when they are in the hierarchy. And once in the hierarchy, each view only updates its trait collection immediately before it performs layout. The best practice is to use traits during layout. For views, that means using the traitCollection from inside the layoutSubviews method. Remember that layoutSubviews runs again any time setNeedsLayout is called on the view, so your implementation should avoid duplicating work if called multiple times. Custom traits are a powerful new feature in iOS 17. They open up an entirely new way for you to provide data to your view controllers and views. As you work with data in your app, consider the following things to help you decide when to define a new custom trait. Traits are a great choice when you need to propagate data to many children, such as from a parent view controller to multiple child view controllers or from a superview to all of its subviews. You can also use traits to pass data to other components that may be nested many layers deep, where you don’t have a direct connection to them. Because traits are inherited through the hierarchy, they can provide context to your views and view controllers about their environment, such as providing information about a containing view controller. While the trait system is powerful, using it to propagate data is not free. For best performance, use traits when they add value, but avoid using traits when you can easily pass data directly. Now you're ready to define your first custom trait. Imagine that I have a Settings screen in my app, and I want to implement a trait that indicates whether a view is contained inside my Settings view controller. I can define a custom trait with just a few lines of code.
To start, I declare a new struct and conform to the UITraitDefinition protocol. I implement the one required static property, defaultValue. This is the default value for the trait when no value has been set. Each trait definition has an associated value type, which is inferred from the defaultValue. In this case, since I am assigning a defaultValue of false, the type of this trait's values is inferred to be Bool. If you’ve ever defined a custom environment key in SwiftUI before, this should feel very familiar. Once you’ve defined a trait, you can immediately use it with new APIs on UITraitCollection and UIMutableTraits. You can think of the trait as a key that you use to get and set values. Inside the new UITraitCollection initializer, I can set a value for my trait by using the subscript operator on UIMutableTraits. And then I can read back the value for the trait by using the subscript operator on UITraitCollection. Adding two simple extensions will let me access this trait using standard property syntax, just like all of the system traits. Here I’ve declared a read-only property in an extension of the immutable UITraitCollection class. And then I’ve declared a read-write property in an extension of the UIMutableTraits protocol. Now that I’ve added these very simple extensions, I can use standard property syntax to access my trait everywhere. Always write these extensions when you define your own custom traits. I have an idea for another custom trait. Imagine I'm building support for custom color themes in my app. I have an enum named MyAppTheme that represents the four different color themes my app supports. The first thing I’ll do is declare a new struct which conforms to the UITraitDefinition protocol. I’ll use the standard theme as the default value for this trait. Since I plan to use this new theme trait in my app’s custom dynamic colors, I indicate that this trait affects color appearance, and the system will automatically redraw views when this trait changes. Traits that affect color appearance are much more expensive, so use this sparingly and only for traits that change infrequently. Traits also have a name, which is used for things like printing the trait in the debugger. By default, it will use the name of the trait type itself, but I can give it a shorter name, like “Theme.” And finally, I can provide an identifier string. The identifier makes the trait eligible for additional features such as encoding. Use reverse-DNS format to ensure each trait's identifier is globally unique in your app.
I want to be able to use regular property syntax to set and get this trait, so I’ll extend UITraitCollection and UIMutableTraits to declare a property, just as I did before in the previous example. And that’s all I need to do to implement a custom theme trait. Now I can start using this new trait. For example, here is how to define a custom dynamic color that changes its appearance based on the theme. I create a new UIColor using the dynamic provider initializer. Inside the closure, I use the theme of the trait collection that gets passed in to determine which color to return. Now I can set this custom background color to a view. Because I indicated that this trait affects color appearance when I defined it, any views using this custom background color will automatically update when the theme changes. When defining a trait, the most important thing to consider is the associated data type of the trait’s values. The best traits are built around value types, including simple structs and enums. Avoid traits that are based around classes in Swift. The most efficient data types for traits are Bool, Int, and Double, or an enum that uses an Int raw value. Enums are one of the most useful data types for traits. Just make sure to explicitly specify Int as the enum's raw data type for maximum efficiency. Any custom struct data types that you use as trait values should have an efficient implementation of the Equatable protocol. The system will compare trait values frequently to determine when traits have changed, so your equal-to function should be as fast as possible.
For those of you with apps that use Objective-C, the new trait system functionality is available there as well. The API for custom traits is different in Swift and Objective-C. However, you can define one custom trait in Swift and one in Objective-C, and have both point to the same underlying data. Refer to the documentation for more details and special considerations.
Once you’ve defined a custom trait, the next step is to populate some data for it in your app's trait hierarchy.
Trait overrides are the mechanism you use to modify data within the trait hierarchy. In iOS 17, it’s easier than ever to apply trait overrides. There’s a new traitOverrides property on each of the trait environment classes, including window scenes, windows, views, view controllers, and presentation controllers. Going back to the illustration of the trait hierarchy, trait overrides change the value for traits at any location in this tree. When you apply a trait override to one of the trait environments in this hierarchy, it modifies the value for that trait in the trait collection of that object and all of its descendants. Taking a parent and child trait environment from the trait hierarchy, here’s how trait overrides affect both. Trait overrides applied to the parent affect the parent’s own trait collection. And then the values from the parent’s trait collection are inherited to the child. Finally, the child's trait overrides are applied to the values it inherited to produce its own trait collection. Think of trait overrides as optional inputs, and the trait collection as the output. Any traits without overrides will inherit from the parent. I’ll go through an example of using trait overrides to change the color theme for specific parts of my app. On the right, I have an illustration of my app’s trait hierarchy. Initially, I haven’t applied any overrides to populate the value for my theme trait, so all of these trait collections have the default value, which is the standard theme. I’ll start by applying a trait override to the root of this hierarchy on the window scene. The traitOverrides property leverages the UIMutableTraits protocol to allow you to easily set trait values. Therefore, you can set override values for custom traits with standard property syntax using the extension to UIMutableTraits I explained earlier. By setting the theme to pastel on the trait overrides of the window scene, all of the windows, view controllers, and views inside of that window scene now inherit the pastel value in their trait collections. So by setting the theme in one place at the root of the hierarchy, I’ve changed the base value that gets propagated to everything in that hierarchy. For example, I can read the theme from the trait collection of any view controller inside that window scene and get back pastel. Then I can use the traitOverrides property on a view deeper in the hierarchy to modify the theme for that view and anything below it. Here I’m setting a monochrome theme trait override for this view. So this monochrome value is what is inherited by its subview, overriding the pastel value from higher up in the hierarchy. You may not see changes to trait overrides reflected immediately in the trait collection. For example, because views update their trait collection right before layout, modifications to a view’s trait overrides aren't reflected in its trait collection until just before it runs layoutSubviews. The traitOverrides property also allows you to check whether overrides are applied and remove overrides altogether. Here is an example of toggling an override by using the contains method to check if an override exists, and the remove method to remove the override entirely. Each time this method is called, it will either remove the existing override or apply a new theme override when there wasn't one already. Trait overrides are an input mechanism to set values. To read trait values, always use the traitCollection property. Reading from traitOverrides when no override has been set will raise an exception.
Here are some performance considerations to keep in mind as you use trait overrides. First, each trait override has a small cost, so only set trait overrides on the places you need them and avoid setting trait overrides that aren’t used. And every time you change a trait override, the system needs to update the trait collection of any descendants in the hierarchy. Therefore, minimize the number of times that you change trait overrides. Finally, trait overrides applied near the root of the hierarchy, such as on the window scene or window, affect everything beneath them. This is very useful, and there are many great use cases for applying trait overrides to the window scene or window. However, when a trait only affects a few views deep in the hierarchy, then apply the trait override to the nearest common ancestor of those views instead, such as a common superview or a view controller. That way, you aren’t paying the cost of propagating the trait to your entire hierarchy when only a small portion of the hierarchy uses that data. Now that you know how to define traits and populate data for them in the hierarchy, you need to handle when they change value.
traitCollectionDidChange is deprecated in iOS 17. When you implement traitCollectionDidChange, the system doesn’t know which traits you actually care about, so it has to call that method every time that any trait changes value. However, most classes only use a handful of traits and don’t care about changes to any others. This is why traitCollectionDidChange doesn’t scale as you add more and more custom traits. In its place, there are new trait registration APIs that are more flexible and improve performance. By registering for changes to specific traits, the system knows exactly which traits you depend on. The new APIs allow you to receive callbacks using the target-action pattern or a closure. And because you no longer need to override a method in your subclass, it’s now easy to observe trait changes from anywhere. I'll start by explaining how to update an existing implementation of traitCollectionDidChange. Here’s my existing implementation. Note how I check whether the horizontalSizeClass trait changed before calling updateViews, as that method only relies on this one trait. If you need to keep using traitCollectionDidChange because you are deploying your app to older iOS versions, make sure that your implementation checks whether the specific traits that you care about have changed. Now I'll replace this implementation with the new trait registration methods in iOS 17. I'll start with the closure-based method. I call registerForTraitChanges and pass an array of traits to register for. There are new UITrait symbols for all of the system traits, such as this one for horizontal size class. Then I pass a closure that is called when those specific traits change. The closure is not called for changes to any other traits, so there's no need to compare old and new trait values here. The object whose traits have changed is passed as the first parameter to the closure. Use this parameter so you don’t have to capture a weak reference to that object. When you're registering for trait changes on self, always write "self: Self" here. You can also observe trait changes for a different trait environment. Here I'm registering for changes to two traits, the horizontal size class and the ContainedInSettings custom trait I defined earlier. The closure executes when either of these traits change on this other view. I write the type of the view I'm registering on as the first parameter of the closure.
Here's an example of the new target-action-based method. Call registerForTraitChanges and pass an array of traits to register for as well as the target and action method to call on changes. The target parameter is optional. If you omit it, the target will be the same object that registerForTraitChanges is called on. In this case, that's self. As with the closure method, you can also register for changes on other trait environments. Here I’m registering for trait changes on another view, but setting up a call to a method on self named handleTraitChange. When registering for trait changes using a target-action, your action method can have zero, one, or two parameters. The first parameter is always the object whose traits are changing. Use this parameter to get the new traitCollection. The second parameter will always be the previous trait collection for that object before the change. In addition to registering for individual traits, you can also register using new semantic sets of system traits. For example, there is systemTraitsAffectingColorAppearance, which returns any system traits that may affect the way system dynamic colors resolve. There’s also systemTraitsAffectingImageLookup, which returns the subset of system traits that are considered when you load an image using UIImage(named:). Pass either of these sets directly to registerForTraitChanges to perform custom invalidation.
Registrations are cleaned up automatically when you use the new methods to register for trait changes. If you have an advanced use case, you can manually unregister using a token that is returned by each of the registration methods. But these cases are very rare, so generally you should just ignore the return value when you call registerForTraitChanges. As you adopt the new trait registration APIs, there are two best practices to keep in mind. First, register only for the traits that you actually depend on so you don't perform work when unrelated traits change value. Finally, try to invalidate in response to trait changes without updating immediately. For example, if you use traits inside of the layoutSubviews method of a view subclass, call setNeedsLayout to invalidate for a trait change. This schedules the view to receive layoutSubviews, but doesn’t perform the update immediately. Now that you can propagate your own data using the trait system in UIKit, it unlocks an entirely new way for you to seamlessly pass data between UIKit and SwiftUI components in your app. Custom traits in UIKit are very similar to custom environment keys in SwiftUI. You can bridge them to access the same data from both UIKit and SwiftUI. Whether you’re embedding SwiftUI components inside of UIKit, or UIKit components inside of SwiftUI, bridged data passes seamlessly between them. You can read and write to the same underlying data using the trait APIs in your UIKit code and the environment APIs in your SwiftUI code. It’s incredibly easy to take the new color theme trait I defined for my app’s UIKit code and bridge it to a corresponding environment key in SwiftUI.
Assuming I have a custom trait in UIKit and a custom environment key in SwiftUI that represent the same data, all I need to do to bridge them is add a conformance to the UITraitBridgedEnvironmentKey protocol. To do this, I implement one method to read the trait from UIKit and return the value to SwiftUI, and one method to write the SwiftUI environment value to the UIKit trait. And now both the UIKit trait and SwiftUI environment key access unified storage, so I can read or write to the same data from components written using either framework. Here is an example of how I can use the bridged trait and environment key. At the root of my app, I apply a trait override for the theme trait onto the UIKit window scene. This propagates the monochrome theme value to everything contained inside that window scene. Then, deeper down inside of a window in that window scene, I have a UIKit collection view. This collection view contains cells, which are configured using UIHostingConfiguration, to display a SwiftUI view in each cell. Inside the SwiftUI CellView, I have a property named "theme," which uses the Environment property wrapper to read the value from the SwiftUI environment. The value in the environment corresponds to the same value for the bridged trait in UIKit. And finally, I use the theme property to control the color of text inside this SwiftUI view. Because SwiftUI automatically tracks data dependencies, if the theme trait override all the way up on the UIKit window scene changes to a different value, my SwiftUI cell view will automatically update to reflect the new theme. The bridging also works in the other direction. Here I have a SwiftUI view that displays my app’s settings. I use the environment modifier to set the standard theme, which will apply to everything in the settings controller. This is conceptually equivalent to applying a trait override in UIKit. Then, in the UIKit-based Settings view controller contained inside the UIViewControllerRepresentable, I read the theme value from the bridged trait and use it to update the title displayed for this view controller. This is how easy it is to seamlessly access data using bridged UIKit traits and SwiftUI environment keys. Now that you've learned about these powerful new features, go and find places in your app where you can leverage the trait system to automatically propagate data by defining your own custom traits. Next, adopt the new traitOverrides property to easily modify data in the trait hierarchy. And then use the more flexible trait registration APIs to create precise dependencies on the exact traits you use. Finally, bridge your custom UIKit traits with custom SwiftUI environment keys so that your data flows seamlessly between UIKit and SwiftUI components in your app. Now it’s up to you to unleash the power of traits. Thanks for watching. ♪ ♪
-
-
1:51 - Working with trait collections
// Build a new trait collection instance from scratch let myTraits = UITraitCollection { mutableTraits in mutableTraits.userInterfaceIdiom = .phone mutableTraits.horizontalSizeClass = .regular } // Get a new instance by modifying traits of an existing one let otherTraits = myTraits.modifyingTraits { mutableTraits in mutableTraits.horizontalSizeClass = .compact mutableTraits.userInterfaceStyle = .dark }
-
9:06 - Implementing a simple custom trait
struct ContainedInSettingsTrait: UITraitDefinition { static let defaultValue = false } let traitCollection = UITraitCollection { mutableTraits in mutableTraits[ContainedInSettingsTrait.self] = true } let value = traitCollection[ContainedInSettingsTrait.self] // true
-
10:23 - Implementing a simple custom trait with a property
struct ContainedInSettingsTrait: UITraitDefinition { static let defaultValue = false } extension UITraitCollection { var isContainedInSettings: Bool { self[ContainedInSettingsTrait.self] } } extension UIMutableTraits { var isContainedInSettings: Bool { get { self[ContainedInSettingsTrait.self] } set { self[ContainedInSettingsTrait.self] = newValue } } } let traitCollection = UITraitCollection { mutableTraits in mutableTraits.isContainedInSettings = true } let value = traitCollection.isContainedInSettings // true
-
11:00 - Implementing a custom theme trait
enum MyAppTheme: Int { case standard, pastel, bold, monochrome } struct MyAppThemeTrait: UITraitDefinition { static let defaultValue = MyAppTheme.standard static let affectsColorAppearance = true static let name = "Theme" static let identifier = "com.myapp.theme" } extension UITraitCollection { var myAppTheme: MyAppTheme { self[MyAppThemeTrait.self] } } extension UIMutableTraits { var myAppTheme: MyAppTheme { get { self[MyAppThemeTrait.self] } set { self[MyAppThemeTrait.self] = newValue } } }
-
12:33 - Using a custom theme trait
let customBackgroundColor = UIColor { traitCollection in switch traitCollection.myAppTheme { case .standard: return UIColor(named: "StandardBackground")! case .pastel: return UIColor(named: "PastelBackground")! case .bold: return UIColor(named: "BoldBackground")! case .monochrome: return UIColor(named: "MonochromeBackground")! } } let view = UIView() view.backgroundColor = customBackgroundColor
-
18:05 - Managing trait overrides
func toggleThemeOverride(_ overrideTheme: MyAppTheme) { if view.traitOverrides.contains(MyAppThemeTrait.self) { // There's an existing theme override; remove it view.traitOverrides.remove(MyAppThemeTrait.self) } else { // There's no existing theme override; apply one view.traitOverrides.myAppTheme = overrideTheme } }
-
21:00 - Trait change handling on older iOS versions
// Efficient implementation that only updates when necessary override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { if traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass { updateViews(sizeClass: traitCollection.horizontalSizeClass) } } func updateViews(sizeClass: UIUserInterfaceSizeClass) { // Update views for the new size class... }
-
21:28 - Registering for trait changes using a closure
// Register for horizontal size class changes on self registerForTraitChanges( [UITraitHorizontalSizeClass.self] ) { (self: Self, previousTraitCollection: UITraitCollection) in self.updateViews(sizeClass: self.traitCollection.horizontalSizeClass) } // Register for changes to multiple traits on another view let anotherView: MyView anotherView.registerForTraitChanges( [UITraitHorizontalSizeClass.self, ContainedInSettingsTrait.self] ) { (view: MyView, previousTraitCollection: UITraitCollection) in // Handle the trait change for this view... }
-
22:48 - Registering for trait changes using a target-action
// Register for horizontal size class changes on self registerForTraitChanges( [UITraitHorizontalSizeClass.self], action: #selector(UIView.setNeedsLayout) ) // Register for changes to multiple traits on another view let anotherView: MyView anotherView.registerForTraitChanges( [UITraitHorizontalSizeClass.self, ContainedInSettingsTrait.self], target: self, action: #selector(handleTraitChange(view:previousTraitCollection:)) ) @objc func handleTraitChange(view: MyView, previousTraitCollection: UITraitCollection) { // Handle the trait change for this view... }
-
24:20 - Registering for changes to system traits affecting color appearance
registerForTraitChanges( UITraitCollection.systemTraitsAffectingColorAppearance, action: #selector(handleColorAppearanceChange) ) @objc func handleColorAppearanceChange() { // Handle the color appearance trait changes... }
-
24:37 - Manually unregistering for trait changes
// Store the returned registration token let registration = registerForTraitChanges([UITraitHorizontalSizeClass.self], action: #selector(handleTraitChange)) // Later, use the stored registration token to manually unregister unregisterForTraitChanges(registration) @objc func handleTraitChange() { // Handle the trait change... }
-
26:19 - Implementing a bridged UIKit trait and SwiftUI environment key
enum MyAppTheme: Int { case standard, pastel, bold, monochrome } // Custom UIKit trait struct MyAppThemeTrait: UITraitDefinition { static let defaultValue = MyAppTheme.standard static let affectsColorAppearance = true } extension UITraitCollection { var myAppTheme: MyAppTheme { self[MyAppThemeTrait.self] } } extension UIMutableTraits { var myAppTheme: MyAppTheme { get { self[MyAppThemeTrait.self] } set { self[MyAppThemeTrait.self] = newValue } } } // Custom SwiftUI environment key struct MyAppThemeKey: EnvironmentKey { static let defaultValue = MyAppTheme.standard } extension EnvironmentValues { var myAppTheme: MyAppTheme { get { self[MyAppThemeKey.self] } set { self[MyAppThemeKey.self] = newValue } } } // Bridge SwiftUI environment key with UIKit trait extension MyAppThemeKey: UITraitBridgedEnvironmentKey { static func read(from traitCollection: UITraitCollection) -> MyAppTheme { traitCollection.myAppTheme } static func write(to mutableTraits: inout UIMutableTraits, value: MyAppTheme) { mutableTraits.myAppTheme = value } }
-
27:01 - Setting a UIKit trait and reading the bridged environment value from SwiftUI
// UIKit trait override applied to the window scene let windowScene: UIWindowScene windowScene.traitOverrides.myAppTheme = .monochrome // Cell in a UICollectionView configured to display a SwiftUI view let cell: UICollectionViewCell cell.contentConfiguration = UIHostingConfiguration { CellView() } // SwiftUI view displayed in the cell, which reads the bridged value from the environment struct CellView: View { @Environment(\.myAppTheme) var theme: MyAppTheme var body: some View { Text("Settings") .foregroundStyle(theme == .monochrome ? .gray : .blue) } }
-
28:16 - Setting a SwiftUI environment value and reading the bridged trait from UIKit
// SwiftUI environment value applied to a UIViewControllerRepresentable struct SettingsView: View { var body: some View { SettingsControllerRepresentable() .environment(\.myAppTheme, .standard) } } final class SettingsControllerRepresentable: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> SettingsViewController { SettingsViewController() } func updateUIViewController(_ uiViewController: SettingsViewController, context: Context) { // Update the view controller... } } // UIKit view controller contained in the SettingsControllerRepresentable class SettingsViewController: UIViewController { override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() title = settingsTitle(for: traitCollection.myAppTheme) } func settingsTitle(for theme: MyAppTheme) -> String { switch theme { case .standard: return "Standard" case .pastel: return "Pastel" case .bold: return "Bold" case .monochrome: return "Monochrome" } } }
-
-
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.