Manage text storage and perform custom layout of text-based content in your app's views using TextKit.

TextKit Documentation

Posts under TextKit tag

47 Posts
Sort by:
Post not yet marked as solved
0 Replies
372 Views
This function on NSTextLayoutManager has the following signature func enumerateTextSegments( in textRange: NSTextRange, type: NSTextLayoutManager.SegmentType, options: NSTextLayoutManager.SegmentOptions = [], using block: (NSTextRange?, CGRect, CGFloat, NSTextContainer) -> Bool ) The documentation here doesn't define what the CGRect and CGFloat passed to block are. However, looking at sample code Using TextKit2 To Interact With Text, they seem to be the frame for the textsegment and baselineposition respectively. But, the textSegmentFrame seems to start at origin.x = 5.0 when text is empty. Is this some starting offset for text segments? I don't seem to be able to find mention of this anywhere.
Posted
by
Post marked as solved
1 Replies
525 Views
This is a tricky one. I have a shipping product which, when compiled under Xcode 14.3.1 works as expected on Sonoma. If the same project is recompiled with Xcode 15, the subclass of NSTextView will not display correctly (text is same color as background). I am also using a custom NSLayoutManager (to draw invisibles). Unfortunately, there is an intermittent aspect to this. I use this subclass in several places and it works on my setup on the main editor, but not with some customers. Then I found a different use of the same subclass that does not work for me. When it does not work, it is consistent for that user. I have manually marked the textViews as using TextKit 1, with no change. I also tried the Clips Bounds to yes, again no change. If I change the class to NSTextView, the text displays properly, but I lose existing functionality. There appears to be some undocumented behavior change in Xcode 15 (or when linking against Sonoma SDK) that for subclasses of NSTextView (stored in XIB files). I know that there is a push to move toward TextKit 2, but it seems TextKit 1 support was possibly changed as well. The text is there and I can edit it, double click, copy and paste it, it is just invisible, when compiled with Xcode 15 (also 15.1). It has to be something very subtle that the subclassed TextView from one XIB will work, but from another XIB will not. Does anyone have any insight into the potential change with TextKit 1 implementation? Thanks.
Posted
by
Post not yet marked as solved
1 Replies
390 Views
I am interested in overriding the default find/replace dialog which comes up from the performFindPanelAction method. I want to add additional things you can search for like text color and text style/format. How would I go about doing this?
Posted
by
Post not yet marked as solved
0 Replies
406 Views
I have been getting crash reports from users of my Mac app on Sonoma 14.0 and 14.1 when typing into an NSTextView subclass. The crash logs I have show involvement of the spell-checking system - NSTestCheckingController, NSSpellChecker, and NSCorrectionPanel. The crash is because of an exception being thrown. The throwing method is either [NSString getParagraphStart:end:contentsEnd:forRange:] or [NSTextStorage ensureAttributesAreFixedInRange:]. I have not yet reproduced the crash. I have tried modifying the reference finding process to simply link every word, via NSStringEnumerationByWords. The text view in question recognizes certain things in the entered text and adds hyperlinks to the text while the user is typing. It re-parses and re-adds the links on every key press (via overriding the didChangeText method), on a background thread. From user reports, I have learned that: The crash only occurs on macOS 14.0 and 14.1, not on previous versions The call stack always involves the spell checker, and sometimes involves adding recognized links to the text storage (the call to DispatchQueue.main.async in the code below) The crash stops happening if the user turns off the system spell checker in System Settings -> Keyboard -> Edit on an Input Source -> Correct Spelling Automatically switch The crash does not happen when there are no links in the text view. Here is the relevant code: extension NSMutableAttributedString { func batchUpdates(_ updates: () -> ()) { self.beginEditing() updates() self.endEditing() } } class MyTextView : NSTextView { func didChangeText() { super.didChangeText() findReferences() } var parseToken: CancelationToken? = nil let parseQueue = DispatchQueue(label: "com.myapp.ref_parser") private func findReferences() { guard let storage = self.textStorage else { return } self.parseToken?.requestCancel() let token = CancelationToken() self.parseToken = token let text = storage.string self.parseQueue.async { if token.cancelRequested { return } let refs = RefParser.findReferences(inText: text, cancelationToken: token) DispatchQueue.main.async { if !token.cancelRequested { storage.batchUpdates { var linkRanges: [NSRange] = [] storage.enumerateAttribute(.link, in: NSRange(location: 0, length: storage.length)) { linkValue, linkRange, stop in if let linkUrl = linkValue as? NSURL { linkRanges.append(linkRange) } } for rng in linkRanges { storage.removeAttribute(.link, range: rng) } for r in refs { storage.addAttribute(.link, value: r.url, range: r.range) } } self.verseParseToken = nil } } } } } I've filed this as FB13306015 if any engineers see this. Can anyone
Posted
by
Post not yet marked as solved
2 Replies
524 Views
I've run into a very strange bug on macOS Sonoma, and I'm guessing it's caused by TextKit 1/2 compatibility issues. Whenever the caret is drawn on the last line of NSTextView, the anti-aliasing or drawing rect or something changes, causing the text in the current line fragment rect to wiggle up and down. See the example below: (Note that in this image, I'm using custom caret drawing to test that if that would solve something — it didn't.) The effect is more apparent on non-Retina displays, but still present on Retina screens as well. I'm using TextKit 1 because I'm maintaining compatibility with older systems, and can't move away just yet. The behavior can't be reproduced on any other OS than Sonoma. My NSTextView is scaled using scaleUnitSquareToSize but no matter the scale, the issue seems to be present. Text view also has custom input attributes, which includes line height, but drawing doesn't appear to be affected by that. Has anyone else encountered similar issues on Sonoma, and do you have any suggestions on how to proceed with debugging this?
Posted
by
Post not yet marked as solved
2 Replies
467 Views
I am developing a richtext editor using UITextView, and I found a BUG of UITextViewDelegate's method: optional func textView( _ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String ) -> Bool This BUG occurs when user tries to delete a selected text. For example: When user deleting "llo" in "hello" by select "llo" press delete The correct range should be NSRange(2, 3),but the actual range is NSRange(1, 4),and replacementText is a empty string. Which means it wants to replace "ello" with "" and it's not right. The final result of this action is "llo" gets replaced by "", which is correct and corrupted with delegate method's range info! No wonder that we can't find this BUG until we test the delegate method. However, when trying to replace "llo" with some text(NOT DELETING), the range info is correct. In conculsion, the caller of the delegate method compute the range wrong when user try to delete a selected text.
Posted
by
Post not yet marked as solved
0 Replies
323 Views
NSMutableAttributedString append NSTextAttachment , and set paragraphStyle.alignment = .justified, will Show Overlap,Word overlap。
Posted
by
Post not yet marked as solved
0 Replies
353 Views
I have a custom subclass to a NSTextContentManager which provides NSTextParagrahs for layout. However, when I have a trailing newline in my content string, the NSTextLayoutFragment does not have a empty NSTextLineFragment indicating a new line. This is in contrast to using the standard NSTextContentStorage. NSTextContentStorage also uses NSTextParagraphs to represent it's text elements. The code I have for both scenarios are Using Default NSTextContentStorage let layoutManager = NSTextLayoutManager() let container = NSTextContainer(size: NSSize(width: 400, height: 400)) layoutManager.textContainer = container let contentStorage = NSTextContentStorage() contentStorage.textStorage?.replaceCharacters(in: NSRange(location: 0, length: 0), with: "It was the best of times.\n") contentStorage.addTextLayoutManager(layoutManager) layoutManager.enumerateTextLayoutFragments(from: contentStorage.documentRange.location, options: .ensuresLayout) { textLayoutFragment in print("defaultTextLineFragments:") for (index, textLineFragment) in textLayoutFragment.textLineFragments.enumerated() { print("\(index): \(textLineFragment)") } print("\n") return true } This outputs defaultTextLineFragments: 0: <NSTextLineFragment: 0x123815a80 "It was the best of times. "> 1: <NSTextLineFragment: 0x123825b00 ""> Using custom subclass to NSTextContentManager class CustomTextLocation: NSObject, NSTextLocation { let offset: Int init(offset: Int) { self.offset = offset } func compare(_ location: NSTextLocation) -> ComparisonResult { guard let location = location as? CustomTextLocation else { return .orderedAscending } if offset < location.offset { return .orderedAscending } else if offset > location.offset { return .orderedDescending } else { return .orderedSame } } } class CustomStorage: NSTextContentManager { let content = "It was the best of times.\n" override var documentRange: NSTextRange { NSTextRange(location: CustomTextLocation(offset: 0), end: CustomTextLocation(offset: content.utf8.count))! } override func textElements(for range: NSTextRange) -> [NSTextElement] { let paragraph = NSTextParagraph(attributedString: NSAttributedString(string: content)) paragraph.textContentManager = self paragraph.elementRange = documentRange return [paragraph] } override func enumerateTextElements(from textLocation: NSTextLocation?, options: NSTextContentManager.EnumerationOptions = [], using block: (NSTextElement) -> Bool) -> NSTextLocation? { // Just assuming static text elements for this example let elements = self.textElements(for: documentRange) for element in elements { block(element) } return elements.last?.elementRange?.endLocation } override func location(_ location: NSTextLocation, offsetBy offset: Int) -> NSTextLocation? { guard let location = location as? CustomTextLocation, let documentEnd = documentRange.endLocation as? CustomTextLocation else { return nil } let offset = CustomTextLocation(offset: location.offset + offset) if offset.compare(documentEnd) == .orderedDescending { return nil } return offset } override func offset(from: NSTextLocation, to: NSTextLocation) -> Int { guard let from = from as? CustomTextLocation, let to = to as? CustomTextLocation else { return 0 } return to.offset - from.offset } } let customLayoutManager = NSTextLayoutManager() let customContainer = NSTextContainer(size: NSSize(width: 400, height: 400)) customLayoutManager.textContainer = customContainer let customStorage = CustomStorage() customStorage.addTextLayoutManager(customLayoutManager) customLayoutManager.enumerateTextLayoutFragments(from: customStorage.documentRange.location, options: .ensuresLayout) { textLayoutFragment in print("customStorage textLineFragments:") for (index, textLineFragment) in textLayoutFragment.textLineFragments.enumerated() { print("\(index): \(textLineFragment)") } print("\n") return true } This output customStorage textLineFragments: 0: <NSTextLineFragment: 0x13ff0c8d0 "It was the best of times. "> I am expecting the two outputs to match (as it's impacting my position calculations for carets during text entry). How would I go about getting the text layout manager to add the extra line fragment while using a custom NSTextContentManager
Posted
by
Post not yet marked as solved
1 Replies
445 Views
I have an interesting challenge, but I do not know where to start. given an NSTextView in an NSScrollView, and enough text in the textView to enable scrolling, how do you determine the location of a single word, and jump to it? we see this sort of behavior in Find panels in Most text editors. So it can be done. additionally, I would very much like to find some kind of predetermined... elements in the text. as a link or an image is not text but can be inline with the text (in Attributed strings) I would like to put an element in the text that is not text, but is targetable and capable of being tagged with some kind of reference. Almost like a bookmark, in which I can scroll to. I have determined that I could use URL Links. But they are so fragile.
Posted
by
Post not yet marked as solved
1 Replies
536 Views
Hi, I new in the Apple Development world using Apple Frameworks. I already got familiar with SwiftUI and I can handle the Swift programming language. However, I need some guidance about what approach I should take to develop a Rich Text Editor for a personal App. I am working on. I could build the Editor using any HTML & any JS Editor Framework like EditorJS. But I prefer to take the native Apple approach, if there is one framework that I can easily use to implement the following capabilities: Text Styling -. Present a paragraph, titles, and subtitle headers with a predefined font size and styles. -. Bulleted and numbered lists Indenting and out-denting of text Formatting -. Predefined Bold, italic, underline, code, strikethrough, sub- and super-scripting Embedding -. Images --> Framework ? -. Iframes --> Framework ? -. Videos --> Framework ? -. Audio --> Framework ? -. Tables and Predefined tables --> Framework ? -. Links -. Drawing canvas with Ipad Pencil --> PencilKit -. PDF view and reviews. --> pdfKit. -. Math formulas. --> Framework ? -. Diagram and flow chart editing --> Framework ? Any recommendation about what framework to use will be appreciated. Thanks in advance.
Posted
by
Post not yet marked as solved
0 Replies
368 Views
With my continued experiments with TextKit2, I'm trying to figure out what sets the properties paragraphSeparatorRange and paragraphContentRange on a NSTextParagraph. These seem to be computed properties (or at least they cannot be directly set via public API). var paragraph = NSTextParagraph(attributedString: NSAttributedString(string: "It was the best of times.\n")) print("attributes: \(paragraph.attributedString.attributes(at: 0, effectiveRange: nil))") print("paragraphSeparatorRange: \(String(describing: paragraph.paragraphSeparatorRange))") print("paragraphContentRange: \(String(describing: paragraph.paragraphContentRange))") In the above example, both paragraphSeparatorRange and paragraphContentRange are nil. However, when using NSTextLayoutManager/NSTextContentStorage they are set. let layoutManager = NSTextLayoutManager() let container = NSTextContainer(size: NSSize(width: 400, height: 400)) layoutManager.textContainer = container let contentStorage = NSTextContentStorage() contentStorage.textStorage = NSTextStorage(string: "It was the best of times.\n") contentStorage.addTextLayoutManager(layoutManager) layoutManager.enumerateTextLayoutFragments(from: contentStorage.documentRange.location, options: .ensuresLayout) { textLayoutFragment in print("layoutFragment: \(textLayoutFragment)") print("textElement: \(String(describing: textLayoutFragment.textElement))") print("textElement.range: \(String(describing: textLayoutFragment.textElement?.elementRange))") let paragraph = textLayoutFragment.textElement as! NSTextParagraph print("paragraphContentRange: \(String(describing: paragraph.paragraphContentRange))") print("paragraphSeparatorRange: \(String(describing: paragraph.paragraphSeparatorRange))") return true } Would appreciate any ideas on how these values are computed/set. Thanks
Posted
by
Post not yet marked as solved
5 Replies
544 Views
If I use -[NSView dataWithPDFInsideRect:] to create a PDF from an NSTextView, I get a single-page PDF with stuff past the bottom cut off. What do I need to do to get pagination to happen? Would embedding the NSTextView in an NSScrollView help?
Posted
by
Post marked as solved
1 Replies
498 Views
Basic HTML strings do not display correctly on macOS Sonoma, I am wondering if there is an alternative technique that can be utilized. There was a minor change in macOS 14.2, which fixed some cases but made others much worse. Consider the following code: override func viewDidLoad() { super.viewDidLoad() let html = """ <table width="100%" border="1" style="color: white"> <tr> <td align="left">Left</td> <td align="right">Right</td> </tr> </table> """ let data = Data(html.utf8) let definition = try! NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: NSNumber(value: String.Encoding.utf8.rawValue)], documentAttributes: nil) let frameRect = NSRect(x: 100, y: 0, width: 300, height: 200) let textView = NSTextView(frame: frameRect) textView.textStorage?.setAttributedString(definition) textView.backgroundColor = .clear view.addSubview(textView) } On macOS 14 (23A5312d) it looks like this (FB13170237): On macOS 14.2 (23C64) it looks like this (FB13465833):
Posted
by
Post not yet marked as solved
1 Replies
410 Views
I'm using UITextView. I set its background color to .white. Initially, the UITextView appears with a white background color. However, after I click on the UITextView, a cursor appears and the background color changes to .systemBackground. I am unable to change this background color by any means.
Posted
by
Post not yet marked as solved
1 Replies
424 Views
I'm having an issue with a TextKit2 NSTextView (AppKit). In my subclass's keyDown, I'm doing a bit of manipulation of the textStorage but it seems that if I try to alter any part of the textStorage where a text paragraph and accompanying fragment have already been created, I then get duplicate paragraphs and fragments. For example, say I want to insert new text at the end of an existing paragraph. I've tried: Inserting a new attr string (with attrs matching the preceding text) at the end of the existing paragraph text Replacing the existing paragraph text with a new attr string that includes the new text First deleting the existing paragraph text and then inserting the new full attr string including the new text I am wrapping those edits in a textContentStorage.performEditingTransaction closure. But in all cases, it seems that TextKit 2 wants to create new NSTextParagraph and NSTextFragment objects and doesn't remove the old ones, resulting in duplicate elements in my UI. Some sample code: let editLocation = editRange.location guard editLocation > 0 else { break } let attrs = textStorage.attributes(at: editLocation, effectiveRange: nil) // paragraphStartLocation is in an NSAttributedString extension not shown guard let paragraphStartLoc = textStorage.paragraphStartLocation(from: editLocation) else { assertionFailure(); return } var paragraphRange = NSRange(location: paragraphStartLoc, length: editLocation - paragraphStartLoc + 1) var fullParagraph = textStorage.attributedSubstring(from: paragraphRange).string fullParagraph += newText let newAttrStr = NSAttributedString(string: fullParagraph, attributes: attrs) textContentStorage.performEditingTransaction { textStorage.deleteCharacters(in: paragraphRange) textStorage.insert(newAttrStr, at: paragraphStartLoc) }
Posted
by
Post not yet marked as solved
0 Replies
396 Views
Hi, I have used some custom Arabic fonts in UITextView to render some Arabic fonts. On iOS 17 the following code is not giving me the correct position of the tapped text in some special cases. Following the code i am using with the TapGestureRecognizer. @objc func arabicTextViewTapped(_ sender: UITapGestureRecognizer) { let attributedString = NSMutableAttributedString(attributedString: arabicTextView.attributedText) attributedString.removeAttribute(.backgroundColor, range: NSRange(location: 0, length: attributedString.length)) let touchPosition = sender.location(in: sender.view) guard let textPosition = arabicTextView.closestPosition(to: touchPosition), let textRange = arabicTextView.tokenizer .rangeEnclosingPosition(textPosition, with: .word, inDirection: .init(rawValue: 1)) else { return } guard let text = arabicTextView.text(in: textRange) else { return } } In the image below if there is some special character (called Ramos e aukaf) as the first character (which in this case is the small first character on the second line) then this code gives the position on the first line. guard let textPosition = arabicTextView.closestPosition(to: touchPosition) In the below case, the above code works perfectly fine as small character is not the first character on the second line. Also this is only happening on ios 17. Any help is highly appriciated.
Posted
by
Post marked as solved
1 Replies
373 Views
I'm trying to render a UIView to PDF, while maintaining text elements as actual text and not rasterizing them. I'm using TextKit 2 backed UITextView and NSTextLayoutManager to provide the contents. However, no matter what I do, UITextView gets rasterized into a bitmap. Here's the basic code: let renderer = UIGraphicsPDFRenderer(bounds: pageRect, format: format) let data = renderer.pdfData { (context) in for page in self.pageViews { context.beginPage() let cgContext = context.cgContext page.layer.render(in: cgContext) } } A page view usually contains one UITextView, which uses somewhat advanced layout, so unfortunately I can't just toss it into a single NSAttributedString and draw into a CoreText context. There's a hack to get UILabels to render themselves as non-rasterized text, and if I understand correctly, it works by just actually drawing them on the current context. Interestingly, when providing UILabel view via NSTextAttachmentViewProvider to a text view using TextKit 2, the UILabels inside a text view won't get rasterized. In the image below, the paragraph is a bitmap, while the heading is real text. This hinted that the actual text fragments are the ones that get rasterized when drawing, not the whole view, and I was right. I can enumerate the text fragments and draw them directly on the context, which makes them remain as text rather than become a bitmap: page.textView?.textLayoutManager?.enumerateTextLayoutFragments(from: location, options: [.ensuresLayout, .estimatesSize, .ensuresExtraLineFragment], using: { fragment in let frame = fragment.layoutFragmentFrame fragment.draw(at: frame.origin, in: cgContext) return true }) This causes other issues, because layout coordinates will be all over the place and not confined to the text view itself (and even more so for my custom NSTextLayoutFragment class). I can get around this, but my text attachments won't get drawn this way either, but display a skewed placeholder icon: I can render them correctly on the context manually: if fragment.textAttachmentViewProviders.count > 0 { if let view = fragment.textAttachmentViewProviders.first?.view { view.layer.render(in: cgContext) return true } } This renders them at 0, 0 though. Edit: Changing bounds or frame for view/layer has no effect. I'm wondering if there is a way to make UITextView and NSTextLayoutManager to draw their contents this way automatically, so the fragments would remain as text in the PDF, rather than become a bitmap? It seems weird that the text view doesn't do this by default like it does on macOS, especially as it appears to be entirely possible. Or, if that's not possible, what's the correct way of drawing my text attachments when enumerating NSTextLayoutFragments?
Posted
by
Post not yet marked as solved
2 Replies
384 Views
Because my MacOS app has a user-programmable interface, my coding creates lots of interface elements programmatically. Soon after an update to MacOS 14.3.1, I found some of these element (NSTextFields) did not draw correctly. I had not changed any code but the appearance changed for the worse (the text in the text field did not appear). I then noticed: The problem occurs when I compile in XCode 15.2 (15C500b) Version and run on computer with Sonoma 14.3.1 (23D60) If I instead compile in XCode Version 14.1 (14B47b) on Ventura 13.0.1 (22A400) and copy the app to my computer with Sonoma 14.3.1 (23D60)all works as expected. These results seem to imply XCode 15.2 does not compile correctly? Is this known issue or is there a fix?
Posted
by