提问人:jeanlain 提问时间:7/28/2023 更新时间:8/2/2023 访问量:58
如何让带有NSImage的CALayer动态适配应用外观?
How to make a CALayer with NSImage dynamically adapt to the app appearance?
问:
我有一个谁有各种各样的.NSView
layer
sublayers
当应用更改其有效外观时,其属性设置为,最终调用:needsDisplay
YES
-(void)updateLayer {
// this is a simplified version
sublayer1.backgroundColor = NSColor.windowBackgroundColor.CGColor;
sublayer2.content = [NSImage imageNamed:@"dynamic image"];
// "dynamic image" is an image asset that has light and dark variants.
}
应用程序启动后,这两个图层都会根据主题正确呈现,但是当应用程序更改主题(未重新启动)时,外观不会更改。的外观确实发生了变化,但我必须重新启动应用程序才能更新到应用程序主题。
使用“动态图像”作为模板图像或原始图像对此问题没有影响。sublayer2
sublayer1
sublayer2
请注意,中使用的相同图像确实会正确更改外观,而无需重新启动应用程序。NSButton
有什么建议吗?
答:
1赞
DonMag
8/2/2023
#1
来自 Apple 的文档:CALayer
contents
讨论
此属性的默认值为 nil。
如果使用图层显示静态图像,则可以将此属性设置为包含要显示的图像的 CGImageRef。(在 macOS 10.6 及更高版本中,还可以将该属性设置为 NSImage 对象。为此属性分配值会导致图层使用您的图像,而不是创建单独的后备存储。
如果图层对象与视图对象相关联,则应避免直接设置此属性的内容。视图和图层之间的相互作用通常会导致视图在后续更新期间替换此属性的内容。
因此,在设置时:
sublayer2.content = [NSImage imageNamed:@"dynamic image"];
该层得到一个 ,而不是一个 ...外观变化将不起作用。CGImageRef
NSImage
一种方法是将 替换为 a 作为子视图。设置属性,就完成了。CALayer
NSImageView
.image
如果需要使用 CALayer
,我们需要从当前外观的图像中获取 。CGImageRef
updateLayer
简单示例:
LayeredView.h
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface LayeredView : NSView
@end
NS_ASSUME_NONNULL_END
LayeredView.m
#import "LayeredView.h"
#import <Quartz/Quartz.h>
@interface LayeredView()
{
CALayer *imgLayer;
NSImage *img;
}
@end
@implementation LayeredView
- (id)initWithFrame:(NSRect)frame {
NSLog(@"%s", __PRETTY_FUNCTION__);
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
NSLog(@"%s", __PRETTY_FUNCTION__);
self = [super initWithCoder:coder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit {
NSLog(@"%s", __PRETTY_FUNCTION__);
[self setWantsLayer:YES];
// load the image
img = [NSImage imageNamed:@"i100x100"];
// create layer
imgLayer = [CALayer new];
// .contentsGravity as desired
imgLayer.contentsGravity = kCAGravityCenter;
// add the layer
[self.layer addSublayer:imgLayer];
}
- (void)updateLayer {
NSLog(@"%s", __PRETTY_FUNCTION__);
[super updateLayer];
// built-in layer animation will result in a "fade"
// so disable it (if desired)
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
if (img) {
// get the CGImageRef from img -- this will reflect light/dark mode
imgLayer.contents = (__bridge id _Nullable)([img CGImageForProposedRect:nil context:nil hints:nil]);
}
// image layer frame as desired
imgLayer.frame = self.bounds;
[CATransaction commit];
}
@end
评论
NSImage
将根据图像的名称缓存图像。我怀疑当您尝试使用 重新获取图像时,它会返回缓存的图像,而不是重新搜索要显示的图像(并在您的资产目录中找到该图像)。在设置之前尝试正确操作,看看这是否会刷新缓存的映像并强制系统在资产目录中搜索该映像。imageNamed
[(NSImage *)sublayer2.content setName: nil];
sublayer2.content
CGImage
NSImage
NSImageView
-recache
NSImageCacheNever
-recache
NSImageView
NSImage
-drawInRect:
-drawRect:
CALayer
contents
-drawInRect:
-drawLayer:InContext:
CALayer
NSImage
CALayer