Mac开发基础06-NSView(二)

要理解 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()    // 如果需要的话,立即进行绘制

使用 NSGraphicsContextCGContext

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

  • 缓存:可以使用 CALayercontents 属性来缓存绘制内容,减少重复绘制开销。
  • 离屏渲染:合理使用 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)")
    }
}
posted @ 2024-08-06 14:09  Mr.陳  阅读(41)  评论(0编辑  收藏  举报