要理解 NSView
更深层的知识,涉及到其渲染机制、事件处理流程、与 CALayer
的关系及性能优化等方面。
1. NSView 绘制和渲染机制
NSView
的绘制过程主要依赖于 drawRect:
(Objective-C)或 draw(_:)
(Swift)方法。这个方法被调用是由系统驱动的,通常发生在需要重新绘制视图的时候,如窗口首次显示、窗口大小改变或强制刷新(调用 setNeedsDisplay:
)。
绘制生命周期
- setNeedsDisplay:
:标记视图需要重新绘制。- displayIfNeeded
:检查视图是否需要绘制,如果需要则调用drawRect:
.- displayRectIgnoringOpacity:
和- display
:可以手动触发视图的绘制。
Objective-C
[view setNeedsDisplay:YES]; // 标记视图需要重新绘制
[view displayIfNeeded]; // 如果需要的话,立即进行绘制
Swift
view.needsDisplay = true // 标记视图需要重新绘制
view.displayIfNeeded() // 如果需要的话,立即进行绘制
使用 NSGraphicsContext
和 CGContext
在 drawRect:
方法中,你可以使用 NSGraphicsContext
获取底层的 CGContext
,以便进行更底层的绘图操作。
Objective-C
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
CGContextRef context = [currentContext CGContext];
CGContextSetFillColorWithColor(context, [[NSColor redColor] CGColor]);
CGContextFillRect(context, dirtyRect);
}
Swift
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
if let context = NSGraphicsContext.current?.cgContext {
context.setFillColor(NSColor.red.cgColor)
context.fill(dirtyRect)
}
}
2. 事件处理流程
响应链
macOS 应用中的事件处理是基于响应链的。响应链起始于窗口,然后传递到最适合处理事件的视图。如果视图不能处理事件,则继续传递给其超级视图,直到到达窗口对象。如果窗口对象也无法处理事件,事件将被忽略。
hitTest:
hitTest:
方法用来确定一个点是否在视图内,它是事件传递过程中关键的一环。
Objective-C
- (NSView *)hitTest:(NSPoint)aPoint {
NSView *hitView = [super hitTest:aPoint];
NSLog(@"Hit view: %@", hitView);
return hitView;
}
Swift
override func hitTest(_ point: NSPoint) -> NSView? {
let hitView = super.hitTest(point)
print("Hit view: \(String(describing: hitView))")
return hitView
}
用户事件处理
Objective-C
- (void)mouseDown:(NSEvent *)event {
NSPoint locationInWindow = [event locationInWindow];
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];
NSLog(@"Mouse down at %@", NSStringFromPoint(locationInView));
}
Swift
override func mouseDown(with event: NSEvent) {
let locationInWindow = event.locationInWindow
let locationInView = self.convert(locationInWindow, from: nil)
print("Mouse down at \(locationInView)")
}
3. NSView 和 CALayer 的关系
wantsLayer
和 layer-backed 视图
通过设置 wantsLayer
,你可以让 NSView
支持 Core Animation 并使用 CALayer
来管理其内容。这样可以利用 Core Animation 的各种特性,比如动画、变换和更高效的离屏渲染。
Objective-C
[view setWantsLayer:YES];
view.layer.backgroundColor = [[NSColor blueColor] CGColor];
Swift
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.blue.cgColor
4. 性能优化
视图层级
避免过于复杂的视图层级(视图层次结构越深,重绘时需要更新的区域也越多)。尽量减少不必要的子视图。
使用 CALayer
- 缓存:可以使用
CALayer
的contents
属性来缓存绘制内容,减少重复绘制开销。 - 离屏渲染:合理使用
shouldRasterize
来进行离屏渲染,提升复杂视图的渲染性能。
Objective-C
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [NSScreen mainScreen].backingScaleFactor;
Swift
view.layer?.shouldRasterize = true
view.layer?.rasterizationScale = NSScreen.main?.backingScaleFactor ?? 1.0
5. 高级自定义绘图
使用 NSBezierPath
NSBezierPath
是用于描述矢量路径的类,适合用于自定义复杂的绘图。
Objective-C
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:dirtyRect];
[[NSColor redColor] setFill];
[path fill];
}
Swift
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let path = NSBezierPath(rect: dirtyRect)
NSColor.red.setFill()
path.fill()
}
6. 自定义 NSView 子类
通过自定义 NSView
子类,可以实现更多高级功能。例如,创建一个交互响应的绘图视图:
Objective-C
@interface CustomView : NSView
@end
@implementation CustomView
- (instancetype)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setWantsLayer:YES];
self.layer.backgroundColor = [[NSColor whiteColor] CGColor];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:dirtyRect];
[[NSColor blueColor] setFill];
[path fill];
}
- (void)mouseDown:(NSEvent *)event {
NSPoint location = [self convertPoint:event.locationInWindow fromView:nil];
NSLog(@"Mouse clicked at %@", NSStringFromPoint(location));
}
@end
Swift
class CustomView: NSView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.wantsLayer = true
self.layer?.backgroundColor = NSColor.white.cgColor
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let path = NSBezierPath(ovalIn: dirtyRect)
NSColor.blue.setFill()
path.fill()
}
override func mouseDown(with event: NSEvent) {
let location = self.convert(event.locationInWindow, from: nil)
print("Mouse clicked at \(location)")
}
}
标签:dirtyRect,06,self,NSView,视图,Mac,super,view
From: https://www.cnblogs.com/chglog/p/18345029