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

好久没发文了,这次来搬运一下我的博客里的文章冒个泡。

我的博客地址为: http://www.nsobjectzyx.tk/,别漏了前面的 www.

引言

在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

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


正题

简单步骤:

  • 创建自定义类型以控制多个文件
  • 重载自定义类型里的方法,完成基本操作
  • UI控件

创建子类以控制多个文件

我们在 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 倍。

NSObject 23786

嗨!年轻的朋友们,迈开你们的双腿,展开你们的双臂,开始行走吧!

相关推荐

3 条评论

  1. EricNTH

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

  2. NSObject 23786

    test

Leave a Reply

微信扫一扫

微信扫一扫

微信扫一扫,分享到朋友圈

macOS开发——一个窗口控制多个文件
返回顶部

显示

忘记密码?

显示

显示

获取验证码

Close