惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
B
Blog
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
Jina AI
Jina AI
博客园 - 聂微东
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
L
LangChain Blog
M
MIT News - Artificial intelligence
Blog — PlanetScale
Blog — PlanetScale
腾讯CDC
酷 壳 – CoolShell
酷 壳 – CoolShell
Y
Y Combinator Blog
F
Fortinet All Blogs
H
Help Net Security
B
Blog RSS Feed
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Apple Machine Learning Research
Apple Machine Learning Research
S
SegmentFault 最新的问题

Peter Steinberger

OpenClaw, OpenAI and the future | Peter Steinberger Shipping at Inference-Speed | Peter Steinberger The Signature Flicker | Peter Steinberger Just Talk To It - the no-bs Way of Agentic Engineering | Peter Steinberger Claude Code Anonymous | Peter Steinberger Live Coding Session: Building Arena | Peter Steinberger My Current AI Dev Workflow | Peter Steinberger Essential Reading for Agentic Engineers - August 2025 | Peter Steinberger Just One More Prompt | Peter Steinberger Poltergeist: The Ghost That Keeps Your Builds Fresh | Peter Steinberger Don't read this Startup Slop | Peter Steinberger Essential Reading for Agentic Engineers - July 2025 | Peter Steinberger Self-Hosting AI Models After Claude's Usage Limits | Peter Steinberger Logging Privacy Shenanigans | Peter Steinberger VibeTunnel's first AI-anniversary | Peter Steinberger Making AppleScript Work in macOS CLI Tools: The Undocumented Parts | Peter Steinberger Peekaboo 2.0 – Free the CLI from its MCP shackles | Peter Steinberger Command your Claude Code Army, Reloaded | Peter Steinberger Essential Reading for Agentic Engineers | Peter Steinberger Slot Machines for Programmers: How Peter Builds Apps 20x Faster with AI | Peter Steinberger My AI Workflow for Understanding Any Codebase | Peter Steinberger stats.store: Privacy-First Sparkle Analytics | Peter Steinberger Showing Settings from macOS Menu Bar Items: A 5-Hour Journey | Peter Steinberger VibeTunnel: Turn Any Browser into Your Mac's Terminal | Peter Steinberger Vibe Meter 2.0: Calculating Claude Code Usage with Token Counting | Peter Steinberger llm.codes: Make Apple Docs AI-Readable | Peter Steinberger Automatic Observation Tracking in UIKit and AppKit: The Feature Apple Forgot to Mention | Peter Steinberger Peekaboo MCP – lightning-fast macOS screenshots for AI agents | Peter Steinberger Migrating 700+ Tests to Swift Testing: A Real-World Experience | Peter Steinberger Commanding Your Claude Code Army | Peter Steinberger
UIAppearance for Custom Views | Peter Steinberger
Peter Steinberger · 2013-02-12 · via Peter Steinberger

UIAppearance is hardly a new technology, since it was first introduced at WWDC 2011, but it still doesn’t have the adoption it deserves (guilty as charged here as well). Since most apps are IOS 5 only now, there’s no excuse anymore to not adopt it. Also, chances are quite high that at least some properties of your classes already support UIAppearance implicitly, since the preprocessor macro to ‘enable’ UIAppearance is actually defined to be empty:

{% blockquote %} #define UI_APPEARANCE_SELECTOR {% endblockquote %}

In the simplest case, add UI_APPEARANCE_SELECTOR to your properties to inform others that this property can be set via an UIAppearance proxy. There are, however, some gotchas that are not clearly mentioned in the documentation, and it’s always interesting how something like this works behind the scenes. (This is not a tutorial — go ahead and read Apple’s documentation on UIAppearance if you’ve never used it before.)

From looking at the debugger, UIAppearance is quite smart and only applies properties before the view is added to a window:

{% img /images/posts/UIAppearance-setter.png %}

UIAppearance is mostly for UIView subclasses, with some exceptions like UIBarItem (and UIBarButtonItem), which internally handle their respective views. For those classes, Apple implemented a custom appearance proxy (_UIBarItemAppearance).

When a custom appearance is set, _UIAppearanceRecorder will track the customizations. There are also certain optimized appearance storage classes like (_UISegmentedControlAppearanceStorage) for UISegmentedControl or _UINavigationBarAppearanceStorage for UINavigationBar.

Let’s start with a simple example, converting this class (taken from my iOS PDF SDK) to work with UIAppearance:

/// Simple rounded label.
@interface PSPDFRoundedLabel : UILabel
/// Corner radius. Defaults to 10.
@property (nonatomic, assign) CGFloat cornerRadius;
/// Label background. Defaults to [UIColor colorWithWhite:0.f alpha:0.6f]
@property (nonatomic, strong) UIColor *rectColor;
@end

@implementation PSPDFRoundedLabel
- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.rectColor = [UIColor colorWithWhite:0.f alpha:0.6f];
        self.cornerRadius = 10.f;
    }
    return self;
}
- (void)setBackgroundColor:(UIColor *)color {
    [super setBackgroundColor:[UIColor clearColor]];
    self.rectColor = color;
}
// drawRect is trivial
@end

Simply adding UI_APPEARANCE_SELECTOR will not work here. One gotcha is that UIAppearance swizzles all setters that have a default apperance, and tracks when they get changed, so that UIAppearance doesn’t override your customizations. This is a problem here, since we use setters in the initializer, and for UIAppearance it now looks as though we already customized the class ourselves. Lesson: Only use direct ivar access in the initializer for properties that comply to UI_APPEARANCE_SELECTOR:

/// Simple rounded label.
@interface PSPDFRoundedLabel : UILabel
/// Corner radius. Defaults to 10.
@property (nonatomic, assign) CGFloat cornerRadius UI_APPEARANCE_SELECTOR;
/// Label background. Defaults to [UIColor colorWithWhite:0.f alpha:0.6f]
@property (nonatomic, strong) UIColor *rectColor UI_APPEARANCE_SELECTOR;
@end

@implementation PSPDFRoundedLabel {
    BOOL _isInitializing;
}
- (id)initWithFrame:(CGRect)frame {
    _isInitializing = YES;
    if (self = [super initWithFrame:frame]) {
        _rectColor = [UIColor colorWithWhite:0.f alpha:0.6f];
        _cornerRadius = 10.f;
    }
    _isInitializing = NO;
    return self;
}
- (void)setBackgroundColor:(UIColor *)color {
    [super setBackgroundColor:[UIColor clearColor]];
    // Check needed for UIAppearance to work (since UILabel uses setters in init)
    if (!_isInitializing) self.rectColor = color;
}
// drawRect is trivial
@end

This class now fully works with UIAppearance. Notice that we had to do some ugly state checking (_isInitializing), because UILabel internally calls self.backgroundColor = [UIColor whiteColor] in the init, which then calls the setRectColor, which would already could as “changed” for UIAppearance. Notice the TaggingApperanceGeneralSetterIMP that Apple uses to track any change to the setter:

{% img /images/posts/UIAppearance-TaggingApperanceGeneralSetterIMP.png %}

I’m using the following code to test the customizations:

{% blockquote %} [[PSPDFRoundedLabel appearanceWhenContainedIn:[PSCThumbnailGridViewCell class], nil] setRectColor:[UIColor colorWithRed:0.165 green:0.226 blue:0.650 alpha:0.800]]; [[PSPDFRoundedLabel appearanceWhenContainedIn:[PSCThumbnailGridViewCell class], nil] setCornerRadius:2]; {% endblockquote %}

We can also use the runtime at any point to query what appearance settings there are for any given class. This is only meant to be used within the debugger, since it uses private API to query _UIAppearance:

{% blockquote %} po [[NSClassFromString(@“_UIAppearance”) _appearanceForClass:[PSPDFRoundedLabel class] withContainerList:@[[PSCThumbnailGridViewCell class]]] valueForKey:@“_appearanceInvocations”] $0 = 0x0bd08cc0 <__NSArrayM 0xbd08cc0>( <NSInvocation: 0xbd08a60> return value: {v} void target: {@} 0x0 selector: {:} _UIAppearance_setRectColor: argument 2: {@} 0xbd08210 , <NSInvocation: 0xbd09100> return value: {v} void target: {@} 0x0 selector: {:} _UIAppearance_setCornerRadius: argument 2: {f} 0.000000 ) {% endblockquote %}

That’s it! The class is fully compatible with UIAppearance. When using this inside a framework, you should write custom UIAppearance rules instead of manually setting the property, to allow to override those rules from the outside (remember, manually setting a property will disable it for apperance usage). +load is a good time for that. There are some more gotchas on UIAppearance, like BOOL not being supported (use NSInteger instead), and some honorable exceptions that do support appearance selectors, like DACircularProgress.

Update: As of iOS 8, BOOL is now supported for UIAppearance.