diff --git a/site/posts/2023-03-20-theming-ios-apps.md b/site/posts/2023-03-20-theming-ios-apps.md index d753d2c..5f9e4c9 100644 --- a/site/posts/2023-03-20-theming-ios-apps.md +++ b/site/posts/2023-03-20-theming-ios-apps.md @@ -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/) diff --git a/site/posts/2023-10-02-custom-traits.md b/site/posts/2023-10-02-custom-traits.md new file mode 100644 index 0000000..b30692d --- /dev/null +++ b/site/posts/2023-10-02-custom-traits.md @@ -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. + + + +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. + + + +It's a pretty minor thing, objectively speaking, but this is a strong contender for my favorite feature of iOS 17.