Add Theming iOS Apps is No Longer Hard

This commit is contained in:
Shadowfacts 2023-10-02 22:58:15 -04:00
parent e89847ce93
commit e8e8c3f244
2 changed files with 55 additions and 0 deletions

View File

@ -72,3 +72,5 @@ if let rootPresentationController = window.value(forKey: "_rootPresentationContr
```
I'll end by reiterating that this is all a giant hack and echoing Christian's sentiment that hopefully iOS ~~16~~ 17 will introduce a proper way of doing this.
**Update:** [Hell yeah](/2023/custom-traits/)

View File

@ -0,0 +1,53 @@
```
title = "Theming iOS Apps is No Longer Hard"
tags = ["swift"]
date = "2023-10-02 22:37:42 -0400"
short_desc = ""
slug = "custom-traits"
```
Well, at least not for the [same reasons](/2023/theming-ios-apps/). I figured I'd write a brief follow-up post, but unless you've been living under a rock, you'll have heard that UIKit gained support for custom traits in `UITraitCollection` with iOS 17. They work very similarly to SwiftUI's environment and are exactly what I wanted.
<!-- excerpt-end -->
To create a custom trait, you define a type conforming to `UITraitDefinition` which serves as the key for your trait:
```swift
struct PureBlackDarkModeTrait: UITraitDefinition {
static let defaultValue = true
static let affectsColorAppearance = true
}
```
The default value is, well, the default value. That is, what will be read when the trait's not explicitly defined in the trait environment. The trait definition also specifies whether this trait can effect the appearance of colors, meaning whether dynamic `UIColor`s should be reëvaluted when the trait's value changes.
Then, you can define an extension on `UITraitCollection` which provides more idiomatic access to the trait (rather than always explicitly looking it up with the type):
```swift
extension UITraitCollection {
var pureBlackDarkMode: Bool {
self[PureBlackDarkModeTrait.self]
}
}
```
One place where UIKit differs slightly from SwiftUI is that trait setters are defined on a separate type: `UIMutableTraits`. So, one more extension:
```swift
extension UIMutableTraits {
var pureBlackDarkMode: Bool {
get { self[PureBlackDarkModeTrait.self] }
set { self[PureBlackDarkModeTrait.self] = newValue }
}
}
```
And with that, I can continue using my custom trait just as I was prior to iOS 16, but without any of the pile of hacks.
<aside class="inline">
Another nice hack-eliminating change is the ability to override traits at any point in the view hierarchy, rather than just on view controllers and presentation controllers. So, to actually set this trait for my entire app, I just set it on the root `UIWindow`'s `traitOverrides` and it propagates downward—rather than having to use SPI to get the root presentation controller and modify it's override trait collection.
</aside>
It's a pretty minor thing, objectively speaking, but this is a strong contender for my favorite feature of iOS 17.