The WWDC 2023 video Unleash the UIKit trait system discusses new UIKit APIs added in iOS 17 related to custom traits in trait collections. The video and its associated code is all in Swift but I wish to use these APIs in an older Objective-C app. I've run into some questions while converting the sample code from Swift to Objective-C.
To start, here is some sample Swift code provided on the Code tab (at timestamp 11:00) of the video:
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 }
}
}
So far, this is what I have for the Objective-C conversion:
MyTraits.h:
@import UIKit;
NS_ASSUME_NONNULL_BEGIN
typedef enum : NSUInteger {
MyAppThemeStandard,
MyAppThemePastel,
MyAppThemeBold,
MyAppThemeMonochrome
} MyAppTheme;
@interface MyAppThemeTrait : NSObject<UINSIntegerTraitDefinition>
@end
@interface UITraitCollection (MyTraits)
@property (nonatomic, readonly) MyAppTheme myAppTheme;
@end
NS_ASSUME_NONNULL_END
MyTraits.m:
#import "Traits.h"
@implementation MyAppThemeTrait
+ (NSInteger)defaultValue { return MyAppThemeStandard; }
+ (BOOL)affectsColorAppearance { return YES; }
+ (NSString *)name { return @"Theme"; }
+ (NSString *)identifier { return @"com.myapp.theme"; }
@end
@implementation UITraitCollection (MyTraits)
- (MyAppTheme)myAppTheme {
return [self valueForNSIntegerTrait:MyAppThemeTrait.class];
}
@end
The sticky point here is how to implement the extension on the UIMutableTraits protocol. Objective-C doesn't support adding a computed property to a protocol through an extension/category. How do you finish implementing custom traits in Objective-C when the language doesn't support what is needed?
It turns out that you don't need the computed property on
UIMutableTraitsfor this to work. That added property in the example Swift code is just a convenience for later use. In Objective-C, you can still make it work, it's just that the final code is slightly more cumbersome.For example, in the Swift code you can create a
UITraitCollectionwith the sample custom trait using code such as:In Objective-C, the code looks as follows:
Since there is no convenience property named
myAppThemeonUIMutableTraits, we need to use the more verbose syntax of callingsetNSIntegerValue:forTrait:(orsetObjectValue:forTrait:orsetCGFloatValue:forTrait:as needed for the given trait).However, there is the added property
myAppThemeonUITraitCollectionso we can now read the value as follows:To see a fully working example in Objective-C, create a new iOS app project. Select Storyboard for the user interface and Objective-C for the language.
Add MyTraits.h and MyTraits.m as shown in the question above.
To work with the theme, we need to add a custom dynamic color. Add the following to MyTraits.h:
Add the following to MyTraits.m:
In ViewController.m, add the following code to
viewDidLoad. This creates a button that overrides themyAppThemetrait with a random theme.Build and run the app. Each time you tap the button the view controller's background color will change depending on the random theme that gets selected. You will also see a log message in the console showing what theme has been selected.