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

Posts under TextKit tag

49 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

What sets NSTextParagraph.paragraphSeparatorRange and paragraphContentRange
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
1
0
445
1w
Build RichTextEditor with Native Apple Framework
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.
1
0
658
Dec ’23
NSScrollView + NSTextView. scrolling to a specific word.
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.
1
0
517
Nov ’23
Seemingly incorrect NSTextLineFragments generated with custom subclass to NSTextContentManager
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
0
0
404
Nov ’23
A BUG of UITextView Delegate Method
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.
2
0
528
Nov ’23
TextKit 1 anti-aliasing on macOS Sonoma
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?
2
0
575
Nov ’23
NSTextView crash with interaction between inserted .link attribute in text storage and NSSpellChecker
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
2
0
502
May ’24
NSTextView subclass not displaying text in Sonoma
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.
1
0
614
Oct ’23
NSTextLayoutManager's enumerateTextSegments parameters documentation
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.
1
0
419
1w
Why was NSAttributedString.Key.verticalGlyphForm Deprecated in macOS 14, and How to Handle Vertical Text Now?
In macOS 14, NSAttributedString.Key.verticalGlyphForm has been deprecated. This key was previously used for rendering text vertically, particularly in languages like Chinese, Japanese, and Korean. I'm curious about the reasons behind its deprecation and would appreciate insights into alternative methods for displaying vertical text.
1
0
497
Oct ’23
TextKit2 textContentManager(_:shouldEnumerate:options:) leaves gaps in layout
Returning false from NSTextContentManagerDelegate.textContentManager(_:shouldEnumerate:options:) produces huge gaps in my layout instead of showing a continuous block of text. Instead of omiting the layout of the hidden element, there is a blank space that shows that appears to have the same size in layout as the omitted text element. Why is this happening and how can I prevent this? Example:
0
0
368
Sep ’23
Text background color for newlines in TextKit 2
When using NSTextLayoutManager.addRenderingAttribute(.backgroundColor, value: NSColor.red, for: range), the background color for a line is only drawn as far as the last visible character. There is also a thin space between the lines where the background color is not visible. Whe using NSLayoutManager.addTemporaryAttribute(.backgroundColor, value: NSColor.red, forCharacterRange: range), the background color is drawn also for newline characters and soft line wraps. I would like to achieve the effect of using NSLayoutManager.addTemporaryAttribute(.backgroundColor, value: NSColor.red, forCharacterRange: range), but since I'm targeting TextKit 2, I have to avoid using NSLayoutManager. Is there a way to achieve this with NSTextLayoutManager or one of the other related classes in TextKit 2?
0
1
411
Sep ’23
Unable to add exclusionPaths to "Meet TextKit 2" sample code
If I set an exclusion path, e.g. in TextDocumentViewController.viewDidLoad() like: textLayoutManager.textContainer?.exclusionPaths = [bezierPath] I am getting a "Unexpectedly found nil while unwrapping an Optional value" crash in TextDocumentView.adjustViewportOffset() because viewportRange is nil on textViewportLayoutController. Setting an exclusion path also causes weird behavior for textLayoutManager.enumerateTextLayoutFragments() which refuses to iterate if the .ensuresLayout option is set. The problem happens in both iOS and macOS (though I only care about iOS). I'm able to get exclusionPaths to work fine if I use UITextView, but the performance is unworkable there. I also was able to set exclusionPaths in the STTextView project in GitHub, but I was unable to identify anything specific that code was doing differently. Anyone have ideas as to what else needs to happen to make exclusionPaths work?
1
0
414
Sep ’23
TextKit2 vs TextKit1 memory usage
I'm working on an app displaying a few hundred custom labels. The custom label is modelled after UILabel. When I implement the custom label using TextKit, the app is using about 0.5 GB of memory. When I implement it using TextKit2, it takes about 1.2 GB. Did anyone notice such a big difference in memory usage between TextKit and TextKit2? For how long will TextKit be around before being deprecated?
1
0
515
Aug ’23
Why does NSAttributedString's NSItemProviderWriting implementation not transfer custom attributes that implement Codable?
I am working on supporting some formatted text editing in my app, and I've been experimenting with copy and paste support for formatted text. I discovered that NSAttributedString implements NSItemProviderWriting, which means I can give it to UIPasteboard via setObjects and all the built-in attributes transfer perfectly if I then paste it into another text view, or even another app that behaves itself. But if I have custom attributes in my attributed string, having their values implement Codable doesn't let them transfer across the clipboard. In my implementation of textPasteConfigurationSupporting(_: transform:), I try to get an attributed string like this: let attr = item.itemProvider.loadObject(ofClass: NSAttributedString.self) { val, err in //...handle here } I get an error like this: Error Domain=NSItemProviderErrorDomain Code=-1000 "Cannot load representation of type com.apple.uikit.attributedstring" UserInfo={NSLocalizedDescription=Cannot load representation of type com.apple.uikit.attributedstring, NSUnderlyingError=0x600003e7bea0 {Error Domain=NSCocoaErrorDomain Code=260 "The file “b036c42113e34c2f9d9af14d6fefcbd534f627d6” couldn’t be opened because there is no such file." UserInfo={NSURL=file:///Users/username/Library/Developer/CoreSimulator/Devices/86E8BDD4-B6AA-4170-B0EB-57C74EC7DDF0/data/Library/Caches/com.apple.Pasteboard/eb77e5f8f043896faf63b5041f0fbd121db984dd/b036c42113e34c2f9d9af14d6fefcbd534f627d6, NSFilePath=/Users/username/Library/Developer/CoreSimulator/Devices/86E8BDD4-B6AA-4170-B0EB-57C74EC7DDF0/data/Library/Caches/com.apple.Pasteboard/eb77e5f8f043896faf63b5041f0fbd121db984dd/b036c42113e34c2f9d9af14d6fefcbd534f627d6, NSUnderlyingError=0x600003e7ac70 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}}} But I tried making my custom attribute values implement NSSecureCoding, and then it worked. Why is Codable conformance not enough here? Is it because the code that serializes and deserializes is still in Objective-C and isn't aware of Codable? Will this change as the open-source Foundation in Swift work continues?
1
0
594
3w