Post not yet marked as solved
In the "old" TextKit, page-based layout is accomplished by providing an array of NSTextContainers to NSLayoutManager, each with its own NSTextView.
TextKit 2, NSTextLayoutManager allows only a single text container. Additionally, NSTextParagraph seems to be the only concrete NSTextElement class. Paragraphs often need to break across page boundaries.
How would one implement page-based layout in TextKit 2?
Post not yet marked as solved
I found that compared with TextKit 1, NSTextLayoutManager does not provide any opportunity to customize line rectangles. In particular, I cannot do much about the frame of a NSTextLayoutFragment. I am curious about the proper approach to:
limit the width and horizontal offset of a paragraph;
add spacing below and above a paragraph;
enclose a paragraph along an arbitrary path.
For the first question, I noticed that in the sample app, when layout comments, BubbleLayoutFragment does not change the positions of any characters. While it did override renderingSurfaceBounds, it merely expands the bounds, without moving its text contents. And there seems to be no way to do so.
For the second question, I noticed that paragraphSpacing and paragraphSpacingBefore set on the paragraph style seems to have no effect. According to the video, all layout fragments are stacked like a list. Therefore, this is an intended feature, right?
Right now, my solutions to the first two questions are:
If I want to control the horizontal position of a paragraph, I could simply set headIndent and trailIndent on the paragraph style.
If I want to add spacing above or below a paragraph, I can add empty lines and set minimalLineHeight for the newline character.
Are those the recommended ways to achieve such effect?
Personally, I am not very interested in the third feature, enclosing a paragraph along an arbitrary path. However, I am indeed curious about how to add paragraph-level attachment, such as an block image/custom view between paragraphs. I think what I could do is:
add an empty new line and set its height to leave enough space for the custom view;
configure a CALayer or even a view and put it where the new line should be;
If I want to layout characters within a strange shape, I could compute the shape's height based on the width, using TextKit 1, and then insert the new line as in step 1.
Is this the intended way to approach such situation?
Post not yet marked as solved
I tried to wrap NSTextView using TextKit 2 into NSViewRepresentable, so I can use it on SwiftUI. But when I try to access textLayoutManager and textContentStorage, they are always nil
import Foundation
import SwiftUI
import Cocoa
struct Editor_macOS: NSViewRepresentable {
func makeNSView(context: Context) -> NSScrollView {
let textContainer = NSTextContainer(size: .zero)
context.coordinator.textLayoutManager.textContainer = textContainer
let textView = NSTextView(frame: .zero, textContainer: textContainer)
context.coordinator.textView = textView
if let docURL = Bundle.main.url(forResource: "menu", withExtension: "rtf") {
do {
print(textView.textLayoutManager)
// try textView.textContentStorage!.textStorage?.read(from: docURL, documentAttributes: nil, error: ())
} catch {
// Could not read menu content.
}
}
textView.isRichText = false
textView.font = NSFont.monospacedSystemFont(ofSize: NSFont.systemFontSize, weight: .regular)
textView.autoresizingMask = [.width]
textView.translatesAutoresizingMaskIntoConstraints = true
textView.isVerticallyResizable = true
textView.isHorizontallyResizable = false
let scrollView = NSTextView.scrollableTextView()
scrollView.hasVerticalScroller = true
scrollView.drawsBackground = false
context.coordinator.scrollView = scrollView
return scrollView
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
}
class Coordinator: NSObject {
var parent: Editor_macOS
var textView: NSTextView?
let textLayoutManager = NSTextLayoutManager()
init(_ parent: Editor_macOS) {
self.parent = parent
}
}
func makeCoordinator() -> Editor_macOS.Coordinator {
Editor_macOS.Coordinator(self)
}
typealias NSViewType = NSScrollView
}
Does anyone have an ideal why is it?
Post not yet marked as solved
What is the recommended approach to rendering text with line numbers in TextKit 2?
Post not yet marked as solved
I can't get NSTextView to work in textkit2 with the following code, textView.textContentStorage , textContentStorage.textStorage is both nil. does anyone have a working example? hope there is a Frameworks Engineer help me out, appreciate so much.
import AppKit
open class MyEditor: NSView {
let textView: NSTextView
let textLayoutManager: NSTextLayoutManager
let textContentStorage: NSTextContentStorage
public init() {
textLayoutManager = NSTextLayoutManager()
textContentStorage = NSTextContentStorage()
let textContainer = NSTextContainer(size: NSSize(width: 200, height: 0.0))
textContainer.widthTracksTextView = true
textContainer.heightTracksTextView = true
textLayoutManager.textContainer = textContainer
textContentStorage.addTextLayoutManager(textLayoutManager)
textView = NSTextView(frame: .zero, textContainer: textContainer)
super.init(frame: .zero)
addSubview(textView)
}
public func setAttributedString(_ attrString: NSAttributedString) {
print(textView.textContentStorage) // always nil
textContentStorage.textStorage?.setAttributedString(attrString)
}
public required init?(coder: NSCoder) {
return nil
}
}
Post not yet marked as solved
The iOS demo project using updateContentSizeIfNeeded to get the layout frame and set the content size for text view. However, it looks like the height is not correct. There is always a huge spacer from the last paragraph to scroll view content bottom. (a.k.a calculated frame large than the real frame.
I'm trying to use the TextKit 2 to layout the text without scrolling enabled. Should I use the old way NSAttirubtedString's boundingRect or there is some new method to calculate the frame size.
Thanks.
Post not yet marked as solved
The iOS demo app set comment with attachment. However, call the textAttachmentViewProviders on the fragment return nothing even the image attachment draw on the screen.
The inline doc is:
// Returns NSTextAttachmentViewProvider associated with the receiver. The property contents are only valid with NSTextLayoutFragmentStateLayoutAvailable.
open var textAttachmentViewProviders: [NSTextAttachmentViewProvider] { get }
I have checked the state. The state is available. How can I get the attachment frame when drawing.
Thanks
Post not yet marked as solved
Good day, simple question. In TextKit2, there seems to be a Rendering Surface Bounds property.
We currently have a text.boundingRect(with...) function call to determine some bounding properties, and this is the accepted proposal for most bounding retrieval, but I am not sure the operation is actually the Rendering Surface Bounds of a provided text, as described in the video.
So ... let say I have such a text, and I want to get that fully enclosing bounds, what would be my best bet (cross-platform)
Although Text will render two lines if you pass it a 2 line String, it will render the text of both lines concatenated if you pass it as a markdown string.
Same if you create an AttributeText before. Examining the AT highlights the markdown structure (NSPresentationIntent, NSInlinePresentationIntent, NSLink, Header, Paragraph, CodeBlock, etc) but this is "flattened" if you will (NSLink works, multiline CodeBlock work).
The problem isn't only present in Text. My use case was CoreText 2: I expected that each AttributeText run would be turned into a text layout fragment but NSTextLayoutManager also flattens my example.
As an example, a screenshot of a markdown file in Xcode and the rendering in the simulator. 2 "paragraphs" aka 2 text layout fragments.
The same test with the EGG recipes rtf file of the CoreText2 WWDC21 session gives predictable results, one text layout fragment per paragraph.
I'm wondering if this is a bug or a "feature" of this markdown first iteration? I tried to access the NSPresentationIntent without much luck to populate CoreText2 myself.
Any hint would be REALLY welcomed.
thank.
Post not yet marked as solved
Hi there,
I am currently trying to extend the TextKit 2 demo project from WWDC 2021 to allow keyboard input under iOS using UITextInteraction. For the most part, it is working well, but the behaviour of the directional cursor text navigation is strange:
When moving up or down, it keeps the horizontal location within the current paragraph. But when crossing an empty line into another paragraph, the former horizontal position is lost and the cursor is reset to the line start.
This is a video showing the behaviour: https://www.icloud.com/iclouddrive/0Xwf9hpb2DcAoU1CNPZnVgwQw#TextKit2_Selection_Bug
I would expect the cursor to restore its former horizontal position in following paragraphs. This is the standard behaviour in UITextView and NSTextView.
Does anybody know if this a bug in TextKit2 or if there is a way to achieve the expected beahviours.
Thanks
Frank
We use NSLayoutManager/NSTextStorage/NSTextContainer to display markdown attributed string on a canvas. MarkDown string was processed properly, but NSLayoutManager just exhibits the plain text. The code is as follows:
var layoutmanager = NSLayoutManager()
var textstorage = NSTextStorage()
var textcontainer = NSTextContainer()
var attrStr = try AttributedString.init(markdown: "**test**", options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace))
var attrString = NSMutableAttributedString(attrStr)
self.textstorage.setAttributedString(attrString!)
// draw text layout
let range = self.layoutmanager.glyphRange(for: self.textcontainer)
self.layoutmanager.drawBackground(forGlyphRange: range, at: self.textLocation)
self.layoutmanager.drawGlyphs(forGlyphRange: range, at: self.textLocation)
Is it because TextKit 1 does not support markdown string display or I miss something else ?
Any help will be appreciated. Thanks!
Post not yet marked as solved
Is it possible to use/draw a SwiftUI view in TextKit 2? The NSTextLayoutManagerDelegate seems to be built around returning a NSTextLayoutFragment.
Since NSTextLayoutFragment is a class, I can't subclass it and use SwiftUI.
I'd love to create my views using SwiftUI, since it's a lot faster than figuring out CGPaths, pixel perfect anti-aliasing, etc.
Thanks!
Post not yet marked as solved
I'm trying to implement custom NSTextContentManager and use it with NSTextView, however it seems that NSTextView expect NSTextContentStorage all the time.
final class MyTextContentManager: NSTextContentManager {
// ...
}
It's added to layout manager, and NSTextView instance finds it properly:
let textContentManager = MyTextContentManager() textContentManager.addTextLayoutManager(textLayoutManager)
however, when I use it, I see errors at:
[MyTextContentManager textStorage]: unrecognized selector sent to instance 0x600003d84870
the textStorage property is part of NSTextStorageObserving, that is not NSTextContentManager interface.
It looks like NSTextView is not ready to work with custom NSTextContentManager. What did I miss?
Post not yet marked as solved
label.text = "Very\u{00AD}VeryVeryVeryVeryVeryLongWordWithASoftHyphenTo"
Post not yet marked as solved
I need to render some view at cursor position, how can I get the current coordinate of cursor?
var location = NSRange(location: textView.selectedRange().location, length: 0)
// the following is not available
textLayoutManager.boundingRect(forGlyphRange: location, in: textContainer)
can we use Textkit2 in production? I think there are lots of methods missing in NSTextLayoutManager while available in NSLayoutManager.
Post not yet marked as solved
Hi! First apologies if I may have missed this in the forums somewhere, but I have been unable to find any documentation on multi-page layouts with the newer text kit and have not been able to create this using/modifying the sample code provided from the WWDC conference earlier this year.
What I am trying to do is create a multipage document where the text is editable and the layout is fairly static. When the text expands in the current view to where there is no room left, a new 'page' will be created and the text will spill over there. Basically standard word or google docs type editor. The position for the pages will need be manipulated depending on some properties.
I also intend in wrapping the solution in SwiftUI since the rest of the app this will be used in is a SwiftUI app. If this isn't possible, I may just stick with TextKit1 or punt altogether.
Thanks!
Sean
Post not yet marked as solved
The way NSTextView is built it's inevitable to use NSTextStorage with TextKit2, however the NSAttributedString uses NSRange vs the TextKit2 family uses NSTextRange for text location, etc. What I struggle with is the relation between these two. I didn't find a convenient translation between these two. Is NSAttributedStrint NSRange length=1 equal to NSTextRange offset 1? I think it's not (at least it's not necessarily true for every NSTextContentManager.
So my question is, given a NSTextRange, what is the corresponding NSRange in NSTextContentStorage.attributedString
Post not yet marked as solved
I want to add characters to the display of my paragraphs without modifying the underlying text storage. To be concrete, let's say that I want to prefix each paragraph with "Whereas, ". Further, when I drag out a text selection on a paragraph that's displayed this way, I do not want "Whereas, " to receive a selection highlight.
Using TextKit 2, it's pretty easy to accomplish the first part using a NSTextContentStorageDelegate whose method textContentStorage(_: NSTextContentStorage, textParagraphWith: NSRange) -> NSTextParagraph? adds my prefix to each paragraph. I modified the WWDC21 demo program to do it. I appreciate how I can avoid glyph munging by using TextKit 2 instead of TextKit 1, it will save a lot of trouble.
If I drag a selection across the prefix, then it receives the selection highlight. What's more, when I drag a selection across the end of the paragraph, the last eight characters (and, I presume, the invisible newline) do not receive the selection highlight. So adding the prefix has skewed the selection indices.
It seems to me that the TextKit 2 designers may intend for me to use a NSTextLocation subclass consisting of a "storage" index and a "display" sub-index. On the storage indices there are the characters of the text storage, and at (storage index, display sub-index)-pairs are the characters of my prefix. While the user drags out a selection, I should subtract from the NSTextSelection any NSTextRanges belonging to non-storage text such as my prefix.
Am I on the right track, so far?
Post not yet marked as solved
The documentation for TextKit 2 is a bit thin. I have a few questions about that:
Is more complete documentation available to Developer Program members?
When can developers expect for the API documentation to be updated?
When will overview documentation, like TextKit 1 has, be available?
Hello :),
I want to have a UIView inside of a UITextView. For that I use the new NSTextAttachmentViewProvider class introduced in iOS 15. The views width should always be equal to the width of the UITextView this width should update when, for example, the screen rotates.
To do that I am using the tracksTextAttachmentViewBounds property inside of a NSTextAttachmentViewProvider subclass. If I understand correctly, if this property is set to true, the function attachmentBounds(for:location:textContainer:proposedLineFragment:position:) of my NSTextAttachmentViewProvider subclass should be used to determine the views bounds. In the code example below I have set it up in that manner, sadly the function is never called. (The storyboard consists of a UIViewController with a UITextView which four constrains (trailing, leading, bottom, top) are set equal to the safe area, nothing special going on). I have also tried to use a NSTextAttachment subclass in which I override the attachmentBounds(for:location:textContainer:proposedLineFragment:position:) function. It is also not called.
The view is appearing, but not with the width and height that I have set in the function (see screenshot below), maybe it is using some default values. When I start typing, the view disappears.
I don't know what I am doing wrong.
Could somebody help me with that problem?
import UIKit
class SomeNSTextAttachmentViewProvider : NSTextAttachmentViewProvider {
override func loadView() {
super.loadView()
tracksTextAttachmentViewBounds = true
view = UIView()
view!.backgroundColor = .purple
}
override func attachmentBounds(
for attributes: [NSAttributedString.Key : Any],
location: NSTextLocation,
textContainer: NSTextContainer?,
proposedLineFragment: CGRect,
position: CGPoint
) -> CGRect {
return CGRect(x: 0, y: 0, width: proposedLineFragment.width, height: 200)
}
}
class ViewController: UIViewController {
@IBOutlet var textView: UITextView?
override func viewDidLoad() {
super.viewDidLoad()
NSTextAttachment.registerViewProviderClass(SomeNSTextAttachmentViewProvider.self, forFileType: "public.data")
let mutableAttributedString = NSMutableAttributedString()
mutableAttributedString.append(NSAttributedString("purple box: "))
mutableAttributedString.append(
NSAttributedString(
attachment: NSTextAttachment(data: nil, ofType: "public.data")
)
)
textView?.attributedText = mutableAttributedString
textView?.font = UIFont.preferredFont(forTextStyle: .body)
}
}