macOS 开发——一个窗口控制多个文件

Hello, 欢迎登录 or 注册!

/ 3评 / 0

本文作者:  本文分类:Apple开发  浏览:428
阅读时间:2541字, 约3-4分钟

作者提醒:请注意,本文写作时间为 2020 年,作者经验尚不成熟,会可能会含有非常多错误或主观的内容,请仔细甄别。

引言

在macOS下进行 Document-Based App 开发时,有时会遇到要同时打开多个文件,却不想把它们分成很多个窗口显示的情况。经过查找资料和实践出真知,我完成了我的应用这一部分的开发。


概念

NSWindowController

NSWindowController 有一个属性叫做 document

unowned(unsafe) var document: AnyObject? { get set } // Swift
// Objective-C
@property(assign) id document; 

这个 document 属性代表了这个窗口控制器实例正在编辑的文档。


NSDocument

成员方法

func makeWindowControllers()

Creates the window controller objects that the document uses to display its content.

可以重载这个方法,来控制它所对应的窗口控制器。


NSDocumentController

控制了整个文件系统的打开、读取、新建。如果要自定义打开操作或者新建文件操作,则需要将它子类化。


正题

简单步骤:

创建子类以控制多个文件

我们在 NSwinndowController class 上进行操作。

由于一个 NSWindowController 只有一个 document 属性,显然无法满足我们多个文件的需求,于是应该新加入一个属性 documents。为了避免重复,可以使用 NSOrderedSet,但它是 Objective-C 硬搬过来的类,很难用,于是我这里用了普通 Swift Array

import Cocoa

class CDMainWindowController: NSWindowController {

    var documents: [CDCodeDocument] = []

}

我们定义一个全局变量 GlobalMainWindowController,表示那个文件窗口,方便操作。

由于需要自定义文件操作,所以子类化一个 NSDocument 类和一个 NSDocumentController 类。

import Cocoa

class CDCodeDocument: NSDocument {

    var contentString = ""

    // ......

}
class CDDocumentController: NSDocumentController {

}

重载自定义类型里的方法,完成基本操作

先在 Window Controller 里写几个基本操作方法。

// 打开或者新建一个文件,把这个文件加入到这个窗口控制器里
func addDocument(_ document: CDCodeDocument) {

    // 重复文件
    if documents.contains(document) {
        return
    }

    self.documents.append(document)
    self.document = document

    // 其他UI更新

}

// 更改目前正在编辑的文件
func setCurrentDocument(index: Int) {

    // 判断越界
    guard index >= 0 && index < self.documents.count else{
        return
    }

    self.document?.removeWindowController(self) // 别忘了!
    let document = self.documents[index]
    document.addWindowController(self)
    self.document = document

    // 其他UI更新

}

之后,更新其他类里面的方法。

先看 Document Controller:

// 打开已有文件
override func openDocument(withContentsOf url: URL, display displayDocument: Bool, completionHandler: @escaping (NSDocument?, Bool, Error?) -> Void) {

    super.openDocument(withContentsOf: url, display: displayDocument) {
        (document, success, error) in
        // 加入到窗口里面
        GlobalMainWindowController.addDocument(document! as! CDCodeDocument)
        completionHandler(document, success, error)
    }

}

// 新建未命名文件
override func openUntitledDocumentAndDisplay(_ displayDocument: Bool) throws -> NSDocument {
    let doc = try super.openUntitledDocumentAndDisplay(displayDocument)
    if displayDocument {
        // 将未命名的文件加入到窗口里面
        GlobalMainWindowController.addDocument(doc as! CDCodeDocument)
    }
    return doc
}

再看 NSDocument 的子类的方法重写:

override func makeWindowControllers() {
    self.addWindowController(GlobalMainWindowController)
}

makeWindowController() 中设置它自己的窗口控制器。


UI更新

视情况进行适当UI更新。比如我开发的这个代码编辑器中,就要更新编辑器中的代码,以及其他一些提示性控件。


The End

这个功能我研究了一两天,最终的效果还是不错的,但有很多小坑,有很多需要注意的点,有时候一个点疏忽了,用户体验就会差 INF 倍。

关于作者

本作品采用 知识共享署名-非商业性使用 3.0 (CC BY-NC 3.0) 许可协议进行许可。
  1. EricNTH说道:

    2020-11-9 20:15 审核通过

  2. EricNTH说道:

    害。win下的mdi视图难写得很。
    您写的好专业啊!

  3. NSObject 23786说道:

    test

发表评论

您的电子邮箱地址不会被公开。