You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建无xib文件的纯代码Cocoa文档应用

纯代码将普通应用转为文档应用(iOS/macOS)

文档应用的核心是基于UIDocument(iOS)或NSDocument(macOS)的文档管理体系,纯代码转换需要完成项目配置、文档类实现、应用生命周期适配、内容视图开发这几个核心步骤,以下是具体操作:

1. 修改项目配置(Info.plist)

首先要在Info.plist中添加文档类型和UTI(统一类型标识符)配置,让系统识别你的应用支持的文档格式:

  • 添加CFBundleDocumentTypes数组,每个元素对应一种文档类型,示例字典配置:
    <dict>
        <key>CFBundleTypeName</key>
        <string>Text Document</string>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>LSHandlerRank</key>
        <string>Owner</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>com.yourcompany.textdoc</string>
        </array>
    </dict>
    
  • 添加UTExportedTypeDeclarations数组,定义自定义UTI,示例字典配置:
    <dict>
        <key>UTTypeIdentifier</key>
        <string>com.yourcompany.textdoc</string>
        <key>UTTypeDescription</key>
        <string>Custom Text Document</string>
        <key>UTTypeConformsTo</key>
        <array>
            <string>public.text</string>
        </array>
        <key>UTTypeTagSpecification</key>
        <dict>
            <key>public.filename-extension</key>
            <array>
                <string>txtdoc</string>
            </array>
        </dict>
    </dict>
    

2. 实现核心文档类

创建继承自UIDocument或NSDocument的类,纯代码实现文档的读写逻辑:

iOS 示例

import UIKit

class TextDocument: UIDocument {
    var content: String = ""

    // 从文件读取内容
    override func load(fromContents contents: Any, ofType typeName: String?) throws {
        guard let data = contents as? Data else { return }
        content = String(data: data, encoding: .utf8) ?? ""
    }

    // 将内容写入文件
    override func contents(forType typeName: String) throws -> Any {
        return content.data(using: .utf8) ?? Data()
    }
}

macOS 示例

import Cocoa

class TextDocument: NSDocument {
    var content: String = ""

    override func read(from data: Data, ofType typeName: String) throws {
        content = String(data: data, encoding: .utf8) ?? ""
    }

    override func data(ofType typeName: String) throws -> Data {
        return content.data(using: .utf8) ?? Data()
    }
}

3. 适配应用生命周期,处理文档打开逻辑

根据平台不同,在应用入口(SceneDelegate/iOS 或 AppDelegate/macOS)中处理文档的创建、打开请求:

iOS(SceneDelegate 版本)

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }
        let window = UIWindow(windowScene: windowScene)
        self.window = window

        // 处理启动时的文档请求(从外部打开文档或新建)
        if let url = connectionOptions.urlContexts.first?.url {
            openDocument(at: url, in: window)
        } else {
            createNewDocument(in: window)
        }
        window.makeKeyAndVisible()
    }

    // 处理外部打开文档的请求
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url, let window = window else { return }
        openDocument(at: url, in: window)
    }

    // 打开已有文档
    private func openDocument(at url: URL, in window: UIWindow) {
        let document = TextDocument(fileURL: url)
        let contentVC = DocumentContentVC(document: document)
        window.rootViewController = UINavigationController(rootViewController: contentVC)
        
        document.open { success in
            if success {
                contentVC.updateContent()
            } else {
                // 处理打开失败逻辑
            }
        }
    }

    // 创建新文档
    private func createNewDocument(in window: UIWindow) {
        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("NewDocument.txtdoc")
        let document = TextDocument(fileURL: tempURL)
        let contentVC = DocumentContentVC(document: document)
        window.rootViewController = UINavigationController(rootViewController: contentVC)
        
        document.save(to: tempURL, for: .forCreating) { success in
            if success {
                contentVC.updateContent()
            } else {
                // 处理创建失败逻辑
            }
        }
    }
}

macOS(AppDelegate 版本)

import Cocoa

class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // 启动时新建文档
        NSDocumentController.shared.newDocument(self)
    }

    // 处理外部打开文档请求
    func application(_ sender: NSApplication, openFile filename: String) -> Bool {
        let url = URL(fileURLWithPath: filename)
        return NSDocumentController.shared.openDocument(withContentsOf: url, display: true) { _, _, error in
            return error == nil
        }
    }
}

4. 实现文档内容视图控制器

纯代码创建用于显示和编辑文档内容的视图控制器,并关联文档实例:

iOS 示例

import UIKit

class DocumentContentVC: UIViewController {
    let document: TextDocument
    private let textView = UITextView()

    init(document: TextDocument) {
        self.document = document
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setupTextView()
        updateContent()
    }

    private func setupTextView() {
        textView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(textView)
        NSLayoutConstraint.activate([
            textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            textView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
        textView.delegate = self
    }

    func updateContent() {
        textView.text = document.content
    }
}

extension DocumentContentVC: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        document.content = textView.text
        // 标记文档有修改,触发自动保存
        document.updateChangeCount(.done)
    }
}

macOS 示例

import Cocoa

class DocumentContentVC: NSViewController {
    let document: TextDocument
    private let textView = NSTextView()

    init(document: TextDocument) {
        self.document = document
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
        view = NSView()
        view.wantsLayer = true
        view.layer?.backgroundColor = NSColor.white.cgColor
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        setupTextView()
        updateContent()
    }

    private func setupTextView() {
        let scrollView = NSScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.documentView = textView
        view.addSubview(scrollView)
        
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        textView.delegate = self
    }

    func updateContent() {
        textView.string = document.content
    }
}

extension DocumentContentVC: NSTextViewDelegate {
    func textDidChange(_ notification: Notification) {
        document.content = textView.string
        document.updateChangeCount(.changeDone)
    }
}

5. 关联文档与视图控制器(macOS 额外步骤)

macOS 需要在文档类中指定视图控制器,纯代码实现:

extension TextDocument {
    override func makeWindowControllers() {
        let contentVC = DocumentContentVC(document: self)
        let windowController = NSWindowController(window: NSWindow(contentViewController: contentVC))
        addWindowController(windowController)
    }
}

内容的提问来源于stack exchange,提问作者vbezhenar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 19:45:21