macOS 开发-NSView

3,703 阅读4分钟

NSView是用于应用程序中渲染、打印以及处理事件的基础容器。

概要

通常我们不需要直接使用NSView对象,而是使用其派生的对象或实现的子类并覆盖其方法来实现所需的行为。NSView(或其子类)的实例通常称为视图对象,或简称为视图。

视图可以处理应用程序的展示内容及其交互。视图对象定义了一个矩形区域,用于绘制和接收鼠标事件。视图还可以处理其他琐事,包括拖动图标以及与类一起使用以支持有效的滚动。

NSView该类的大多数功能都是由AppKit自动调用的。除非您NSView在运行时实现视图层次结构的具体子类或与视图层次结构的内容紧密联系,否则您无需了解此类的接口。对于任何视图,可以按原样使用许多方法。通常使用以下方法。

  • frame返回视图的位置和大小。
  • bounds返回视图的内部原点和大小。
  • needsDisplay视图是否需要重绘对象。
  • window返回NSWindow包含该NSView对象的对象。
  • draw(_:)绘制视频对象(所有子类都必须实现此方法,但是很少显式调用它)。

基础功能

创建实例

常用api:

//初始化对象
init(frame: NSRect)
// 将视图恢复到初始状态,以便可以重用
func prepareForReuse()

我们可以通过init来创建视图,并可以通过prepareForReuse方法来重置视图:

lazy var containerView: NSView = {
    let view = NSView(frame: NSRect(x:20, y: 20, width: 100, height: 100))
    view.wantsLayer = true
    view.layer?.backgroundColor = NSColor.red.cgColor
  	view.alphaValue = 0.5
    return view
}()

private func resetView() {
  	// 将视图的Alpha设为1.0,隐藏状态设为false。 子类可以重写此方法,并使用它使视图返回其初始状态。可以参考TableView的复用机制
    self.containerView.prepareForReuse()
}

管理视图层次结构

管理视图层结构常用api:

// 获取当前视图的父视图
var superview: NSView?
// 当前视图中所有子视图
var subviews: [NSView]
// 视图的窗口对象(如果已安装在窗口中)
var window: NSWindow?
// 将视图添加为指定视图的子视图
func addSubview(NSView)
// 在视图的子视图中插入一个视图,以便将其显示在另一个视图的上方或下方
func addSubview(NSView, positioned: NSWindow.OrderingMode, relativeTo: NSView?)
// 从父视图中移除
func removeFromSuperview()

修改frame

常用的api:

// 视图的框架矩形,用于定义其在父视图的坐标系中的位置和大小
var frame: NSRect
// 将视图的原点设置为指定点
func setFrameOrigin(NSPoint)
// 将视图设置为指定的尺寸,在其父视图内调整其大小而不影响其坐标系
func setFrameSize(NSSize)
// 旋转角度(以度为单位)应用于相对于其超级视图坐标系的视图
var frameRotation: CGFloat

修改bounds

常用的api:

// 视图的边界矩形,表示其在自己的坐标系中的位置和大小
var bounds: NSRect
// 将视图的边界矩形的原点设置为指定点
func setBoundsOrigin(NSPoint)
// 将视图的边界矩形的大小设置为指定的尺寸
func setBoundsSize(NSSize)
// 旋转角度(以度为单位)
var boundsRotation: CGFloat

通过boundsRotation来图一朵小🌸🌸:

for i in 1...18 {
    let v = NSView(frame: NSRect(x:60, y: 60, width: 100, height: 100))
    v.wantsLayer = true
    v.alphaValue = 0.5
    v.layer?.backgroundColor = NSColor.red.cgColor
    v.boundsRotation = CGFloat(i * 10)
    view.addSubview(v)
}

frame 与 bounds 区别

frame是相对父视图的坐标系中的位置和大小,bounds是相对坐标系统中的位置和大小。

管理视图层

管理视图层常用api:

// 设置为true时,转换成可支持Layer的视图,即使用一个CALayer对象来管理呈现内容,会隐式地将所有子视图支持该属性
var wantsLayer: Bool
// 视图用作其后备存储的核心动画层
var layer: CALayer?

默认情况下,视图是没有创建layer的,也即不做任何处理时,我们访问layer是为空的,当我们设置wantsLayertrue时,视图会创建一个CALayer对象来管理呈现内容,我们才能通过layer来修改视图的显示内容,比如视图的背景图:

lazy var containerView: NSView = {
    let view = NSView(frame: NSRect(x:20, y: 20, width: 100, height: 100))
    view.wantsLayer = true
    view.layer?.backgroundColor = NSColor.red.cgColor
    return view
}()

绘制视图

绘制视图常用api:

// 被子类重写以在指定的矩形内绘制视图的图像
func draw(NSRect)

如果我们实现自己的视图,则必须要重写darw()方法,比如我们要实现一个圆形的视图类:

class CircleView: NSView {
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        setCornerRadius(dirtyRect.size)
    }
    
    override func setFrameSize(_ newSize: NSSize) {
        super.setFrameSize(newSize)
        setCornerRadius(newSize)
    }
    
    override func setBoundsSize(_ newSize: NSSize) {
        super.setBoundsSize(newSize)
        setCornerRadius(newSize)
    }
    
    private func setCornerRadius(_ size: NSSize) {
        self.wantsLayer = true
        self.layer?.cornerRadius = size.width / 2;
        self.layer?.masksToBounds = true
    }
}

我们需要在darw方法中,处理我们需要绘制的内容。

小结

这里初步了解NSView的基本使用,接下来,我们会在下一节,我们将会通过实现一个小应用来扩展了解。欢迎关注我的公众号,后继还有视频教程:

关注公众号,可以查看后继的视频教程