WWDC 2023 视频释放 UIKit 特征系统讨论了 iOS 17 中添加的与特征集合中的自定义特征相关的新 UIKit API。该视频及其相关代码均使用 Swift 编写,但我希望在较旧的 Objective-C 应用程序中使用这些 API。我在将示例代码从 Swift 转换为 Objective-C 时遇到了一些问题。
首先,这里是视频的“代码”选项卡(时间戳 11:00)上提供的一些示例 Swift 代码:
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 }
}
}
到目前为止,这就是我的 Objective-C 转换:
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
我的特征.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
这里的难点是如何在
UIMutableTraits
协议上实现扩展。 Objective-C 不支持通过扩展/类别向协议添加计算属性。当 Objective-C 语言不支持所需内容时,如何完成自定义特征的实现?
事实证明,您不需要
UIMutableTraits
上的计算属性即可实现此功能。示例 Swift 代码中添加的属性只是为了方便以后使用。在 Objective-C 中,你仍然可以让它工作,只是最终的代码稍微麻烦一些。
例如,在 Swift 代码中,您可以使用以下代码创建具有示例自定义特征的
UITraitCollection
:
let myTraits = UITraitCollection { mutableTraits in
mutableTraits.myAppTheme = .pastel // <-- custom trait here
mutableTraits.horizontalSizeClass = .regular
}
在 Objective-C 中,代码如下所示:
UITraitCollection *myTraits = [UITraitCollection traitCollectionWithTraits:^(id<UIMutableTraits> _Nonnull mutableTraits) {
[mutableTraits setNSIntegerValue:MyAppThemePastel forTrait:MyAppThemeTrait.class];
mutableTraits.horizontalSizeClass = UIUserInterfaceSizeClassRegular;
}];
由于
myAppTheme
上没有名为 UIMutableTraits
的便利属性,我们需要使用更详细的语法来调用 setNSIntegerValue:forTrait:
(或根据给定特征的需要使用 setObjectValue:forTrait:
或 setCGFloatValue:forTrait:
)。
但是,
myAppTheme
上添加了属性UITraitCollection
,因此我们现在可以读取该值,如下所示:
MyAppTheme theme = myTraits.myAppTheme;
要查看 Objective-C 中完整工作的示例,请创建一个新的 iOS 应用程序项目。选择 Storyboard 作为用户界面,选择 Objective-C 作为语言。
添加 MyTraits.h 和 MyTraits.m,如上面的问题所示。
要使用主题,我们需要添加自定义动态颜色。将以下内容添加到 MyTraits.h:
@interface UIColor (MyTraits)
@property (nonatomic, readonly, class) UIColor *customBackgroundColor;
@end
将以下内容添加到 MyTraits.m:
@implementation UIColor (MyTraits)
+ (UIColor *)customBackgroundColor {
return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
// This example uses some randomly chosen colors. Pick your own to suit your needs
switch (traitCollection.myAppTheme) {
case MyAppThemeStandard:
return [UIColor colorWithRed:0 green:1 blue:0 alpha:1];
case MyAppThemePastel:
return [UIColor colorWithRed:0.39 green:0.58 blue:0.93 alpha:1];
case MyAppThemeBold:
return [UIColor colorWithRed:1 green:0 blue:1 alpha:1];
case MyAppThemeMonochrome:
return [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1];
default: // make the compiler happy
return UIColor.systemBackgroundColor; // This shouldn't happen
}
}];
}
@end
在ViewController.m中,将以下代码添加到
viewDidLoad
。这将创建一个使用随机主题覆盖 myAppTheme
特征的按钮。
// Let's see when the theme changes
[self registerForTraitChanges:@[ MyAppThemeTrait.class ] withHandler:^(__kindof id<UITraitEnvironment> _Nonnull traitEnvironment, UITraitCollection * _Nonnull previousCollection) {
NSLog(@"VC change myAppTheme: %ld", traitEnvironment.traitCollection.myAppTheme);
}];
// Set the background to the custom theme color
self.view.backgroundColor = UIColor.customBackgroundColor;
// Setup a button to randomly change the theme
__weak typeof(self) weakSelf = self;
UIButtonConfiguration *cfg = [UIButtonConfiguration grayButtonConfiguration];
cfg.title = @"Change";
UIButton *btn = [UIButton buttonWithConfiguration:cfg primaryAction:[UIAction actionWithHandler:^(__kindof UIAction * _Nonnull action) {
NSInteger theme = arc4random() % 4; // Pick a random theme
// Set the new theme
[weakSelf.traitOverrides setNSIntegerValue:theme forTrait:MyAppThemeTrait.class];
// If we could add the convenience property to UIMutableTrait then this line would simply be:
// weakSelf.traitOverrides.myAppTheme = theme;
}]];
btn.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:btn];
// Put the button in the center of the screen
[NSLayoutConstraint activateConstraints:@[
[btn.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[btn.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
]];
构建并运行应用程序。每次点击按钮时,视图控制器的背景颜色都会根据选择的随机主题而改变。您还将在控制台中看到一条日志消息,显示已选择的主题。