Ericnth的小站

  • 引言
  • 概念
  • NSWindowController
  • NSDocument
  • NSDocumentController
  • 正题
  • 创建子类以控制多个文件
  • 重载自定义类型里的方法,完成基本操作
  • UI更新
  • The End
  • 你可能还想了解...
  • 首页
  • 编程学习笔记
  • 系统与软件
  • 摄影
  • 随笔
  • 论坛
  • 公告

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

  • 23 786
  • 2020-11-07
  • 0

作者提醒:请注意,本文写作时间为 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

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


正题

简单步骤:

  • 创建自定义类型以控制多个文件
  • 重载自定义类型里的方法,完成基本操作
  • 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 倍。

你可能还想了解...

  • WWDC2022总结(下)
  • RingtoneHY 项目详解(全)
  • WWDC2022总结(上)
  • 关于WWDC2022的预测
  • iOS14beta2发布了,你升级了吗?
© 2023 Ericnth的小站
Theme by Wing
沪ICP备2020025694号 沪公网安备31011202012861号
  • {{ item.name }}
  • {{ item.name }}