可滚动 NSTextView 与自定义 NSTextStorage 进行格式化

发布于 2025-01-10 20:31:10 字数 3330 浏览 7 评论 0原文

我正在尝试制作一个适用于 Mac 操作系统的文本编辑器。我将 NSTextView 与自定义 NSTextStorage 类一起使用。它将诸如粗体等属性应用于NSAttributableStrings

这一切似乎工作正常,如下面的屏幕截图之一所示。这是一个带有自定义 NSTextStorage 类的 NSTextView 。它通过 NSAttributeableString

NSTextView 没有滚动和用于格式化的自定义 TextStorage

但是,一切都相同,但从 Apple 提供的函数获取可滚动的 NSTextView NSTextView.scrollableTextView() 它根本不显示任何文本。即使您可以在屏幕截图中看到 NStextView 实际上是可见的。此外,将鼠标移到编辑器上会将光标更改为编辑器光标。但我无法选择、输入或执行任何操作。

可滚动文本与客户 NSTextStorage 的视图,不显示任何内容

执行与上面完全相同的操作,但不向文本视图提供文本容器确实表明它已正确连接,因为我确实获得了可滚动的文本视图 然后。滚动实际上起作用的地方,但格式当然不再应用。

NSTextView 其中滚动有效,但未应用格式

所以我对现在必须做什么感到困惑。

这基本上是我的设置:

//
//  TextDocumentViewController.swift
//
//  Created by Matthijn on 15/02/2022.
//  Based on LayoutWithTextKit2 Sample from Apple

import Foundation
import AppKit

class TextDocumentViewController: NSViewController {
    
    // Extends NSTextStorage, applies attributes to NSAttributeAbleString for formatting
    private var textStorage: TextStorage

    // Not custom yet, default implementation - do I need to subclass this specifically and implement something to support the scrolling behaviour? Which seems weird to me since it does work without scrolling support 
    private var layoutManager: NSLayoutManager
    // Also default implementation
    private var textContainer: NSTextContainer
    
    private var textDocumentView: NSTextView
    private var scrollView: NSScrollView

    required init(content: String) {
        textStorage = TextStorage(editorAttributes: MarkdownAttributes)


        layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)
        
        
        textContainer = NSTextContainer()
        // I'm not 100% sure if I need this on false or true or can just do defaults. No combination fixes it
        // textContainer.heightTracksTextView = false
        // textContainer.widthTracksTextView = true
        
        layoutManager.addTextContainer(textContainer)
        
        scrollView = NSTextView.scrollableTextView()
        textDocumentView = (scrollView.documentView as! NSTextView)
    
        // Basically commenting this out, stops applying my NSTextContainer, NSLayoutManager and NSTextContainer, but then of course the formatting is not applied. This one line changes it between it works without formatting, or it doesn't work at all. (Unless I have my text view not embedded in a scroll view) then it works but the scrolling of course then does not work.       
        textDocumentView.textContainer = textContainer
    
        textDocumentView.string = content
        textDocumentView.isVerticallyResizable = true
        textDocumentView.isHorizontallyResizable = false
        
        super.init(nibName: nil, bundle: nil)
    }
    
    override func loadView() {
        view = scrollView
    }
}

I'm trying to make a text editor with formatting for Mac OS. Which I have working using an NSTextView together with a custom NSTextStorage class. Which applies attributes like bold etc to NSAttributableStrings.

This all seems to work fine as seen in screenshot one below. Which is an NSTextView with a custom NSTextStorage class attached to it. Which applies the formatting through attributes on an NSAttributeableString

NSTextView without scrolling and custom TextStorage for formatting

However, having everything the same, but getting a scrollable NSTextView from the Apple supplied function NSTextView.scrollableTextView() it does not display any text at all. Even though you can see in the screenshot that the NStextView is actually visible. Also, moving my mouse over the editor changes the cursor to the editor cursor. But I can't select, type or do anything.

Scrollable text view with customer NSTextStorage, shows nothing

Doing the exact same thing as above, but not supplying a text container to the text view does show that it is wired up correctly, since I do get a scrollable text view then. Where the scrolling actually works, but then of course the formatting is no longer applied.

NSTextView where scrolling works, but formatting is not applied

So I'm confused on what I have to do now.

This is basically my setup:

//
//  TextDocumentViewController.swift
//
//  Created by Matthijn on 15/02/2022.
//  Based on LayoutWithTextKit2 Sample from Apple

import Foundation
import AppKit

class TextDocumentViewController: NSViewController {
    
    // Extends NSTextStorage, applies attributes to NSAttributeAbleString for formatting
    private var textStorage: TextStorage

    // Not custom yet, default implementation - do I need to subclass this specifically and implement something to support the scrolling behaviour? Which seems weird to me since it does work without scrolling support 
    private var layoutManager: NSLayoutManager
    // Also default implementation
    private var textContainer: NSTextContainer
    
    private var textDocumentView: NSTextView
    private var scrollView: NSScrollView

    required init(content: String) {
        textStorage = TextStorage(editorAttributes: MarkdownAttributes)


        layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)
        
        
        textContainer = NSTextContainer()
        // I'm not 100% sure if I need this on false or true or can just do defaults. No combination fixes it
        // textContainer.heightTracksTextView = false
        // textContainer.widthTracksTextView = true
        
        layoutManager.addTextContainer(textContainer)
        
        scrollView = NSTextView.scrollableTextView()
        textDocumentView = (scrollView.documentView as! NSTextView)
    
        // Basically commenting this out, stops applying my NSTextContainer, NSLayoutManager and NSTextContainer, but then of course the formatting is not applied. This one line changes it between it works without formatting, or it doesn't work at all. (Unless I have my text view not embedded in a scroll view) then it works but the scrolling of course then does not work.       
        textDocumentView.textContainer = textContainer
    
        textDocumentView.string = content
        textDocumentView.isVerticallyResizable = true
        textDocumentView.isHorizontallyResizable = false
        
        super.init(nibName: nil, bundle: nil)
    }
    
    override func loadView() {
        view = scrollView
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蓝礼 2025-01-17 20:31:10

您实际上可以使用 scrollableTextView() 创建 NSScrollView 实例,并且可以获得隐式创建的 documentView (NSTextView)

最后,可以将 documentView 的现有 LayoutManager 分配给继承自 NSTextStorage 的自己的 TextStorage 类。

viewDidLoad 中,您可以使用 addSubview: 以传统方式将 scrollView 添加到视图中。对于 AutoLayout,一如既往,translatesAutoresizingMaskIntoConstraints 必须设置为 false

使用 widthAnchorgreaterThanOrEqualToConstant 您可以定义最小尺寸,因此用户无法将其周围的窗口变小。该结构还允许以后使用附加的粘性视图(例如面包屑视图等)进行潜在的简单扩展。

代码

如果您以这种方式实现它,那么对于一个小型的最小测试,它可能看起来像这样。

import Cocoa


class ViewController: NSViewController {

    private var scrollView: NSScrollView
    private var textDocumentView: NSTextView

    required init(content: String) {
        scrollView = NSTextView.scrollableTextView()
        let textDocumentView = scrollView.documentView as! NSTextView
        self.textDocumentView = textDocumentView
        let textStorage = TextStorage(editorAttributes: MarkdownAttributes())
        textStorage.addLayoutManager(textDocumentView.layoutManager!)

        textDocumentView.string = content

        super.init(nibName: nil, bundle: nil)

    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.widthAnchor.constraint(greaterThanOrEqualToConstant: 200.0),
            scrollView.heightAnchor.constraint(greaterThanOrEqualToConstant: 200.0),
        ])
    }
    
    override func loadView() {
      self.view = NSView()
    }
    
}

测试

以下是一个快速测试,显示显示和滚动都按设置的预期工作:

演示

You can actually create the NSScrollView instance with scrollableTextView() and you can get the implicitly created documentView (NSTextView).

Finally, one can assign the existing LayoutManager of the documentView to the own TextStorage class inheriting from NSTextStorage.

In viewDidLoad you could then add the scrollView traditionally to the view using addSubview:. For AutoLayout, as always, translatesAutoresizingMaskIntoConstraints must be set to false.

With widthAnchor and a greaterThanOrEqualToConstant you can define a minimum size, so the window around it cannot be made smaller by the user. The structure also allows potential later simple extensions with additional sticky views (e.g. breadcrumb view etc).

Code

If you implement it this way, then for a small minimal test it might look something like this.

import Cocoa


class ViewController: NSViewController {

    private var scrollView: NSScrollView
    private var textDocumentView: NSTextView

    required init(content: String) {
        scrollView = NSTextView.scrollableTextView()
        let textDocumentView = scrollView.documentView as! NSTextView
        self.textDocumentView = textDocumentView
        let textStorage = TextStorage(editorAttributes: MarkdownAttributes())
        textStorage.addLayoutManager(textDocumentView.layoutManager!)

        textDocumentView.string = content

        super.init(nibName: nil, bundle: nil)

    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.widthAnchor.constraint(greaterThanOrEqualToConstant: 200.0),
            scrollView.heightAnchor.constraint(greaterThanOrEqualToConstant: 200.0),
        ])
    }
    
    override func loadView() {
      self.view = NSView()
    }
    
}

Test

Here's a quick test showing that both display and scrolling work as expected with the setup:

demo

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文