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

TextKit Documentation

Posts under TextKit tag

48 Posts
Sort by:
Post not yet marked as solved
0 Replies
43 Views
Hello, I'm trying to get a better understanding of the SMS.db tables and fields. Does anybody have a document that describes each table and the fields within that table? I'd like to understand what each field in the Messages Table is used for but it would be a bonus if the is info is also available for the other tables contained in the SMS.db as well.
Posted
by ED2017.
Last updated
.
Post not yet marked as solved
2 Replies
173 Views
In TextKit 1 we have the method NSTextStorage.addLayoutManager(_:) that allows to show the same text in multiple text views. This method exists with NSLayoutManager but not with NsTextLayoutManager. Is there a way to achieve the same thing with TextKit 2?
Posted
by Nickkk.
Last updated
.
Post not yet marked as solved
0 Replies
108 Views
Hi, I'm interested in creating a font with alternate glyphs that are accessed via Unicode variation selectors, but I'm getting the impression that macOS does not really support these, generally. For example, I'm unable to enter the variation selectors (other than U+FE0F) into a document in Pages or TextEdit using Unicode Hex Input. Is my hunch correct? I'm interested in using the standardized variations listed here: http://unicode.org/Public/UNIDATA/StandardizedVariants.txt Specifically, slashed zero: 0030 FE00; short diagonal stroke form; # DIGIT ZERO Thanks for any insight you may be able to provide.
Posted Last updated
.
Post not yet marked as solved
0 Replies
125 Views
Starting with the macOS version 14.x.x and TextKit1, selecting multiple lines of text triggers a text replacement bug: some of the text on one of the selected lines inadvertently replaces a portion of the selected text. For example, the bug is exhibited when selecting the following lines: Carnaroli, Maratelli, or Vialone Nano are best Vialone Nano cooks quickly – watch it! It also absorbs condiments nicely. Avoid Baldo, Originario, Ribe and Roma To trigger the bug, select the three line paragraph using either the cursor or shift with arrow keys. Notice that a portion of the selected text was replaced. Command-Z to undo will allow you to repeat the undesired behavior. In this case, "e Nano cooks quickly - " is replaced by "Baldo, Originario, Ribe." This does not occur with all strings or selected strings, but in cases where it does occur, it is perfectly reproducible. It does not occur on iOS. Pasteboard contents are irrelevant. After triggering the bug repeatedly, at some point it stops occurring. Why does this bug occur? How can it be fixed? Here is some sample code to reproduce the issue. @end @implementation TestNoteViewController - (void)viewDidLoad { [super viewDidLoad]; [self createTextView]; } - (void)createTextView { NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:self.note.text attributes:nil]; NSTextStorage *textStorage = [NSTextStorage new]; [textStorage appendAttributedString:attrString]; CGRect newTextViewRect = self.view.bounds; // Create the layout manager NSLayoutManager *layoutManager = [NSLayoutManager new]; [textStorage addLayoutManager:layoutManager]; // Create a text container NSTextContainer *container = [[NSTextContainer alloc] initWithSize:CGSizeMake(newTextViewRect.size.width, CGFLOAT_MAX)]; [layoutManager addTextContainer:container]; // Create and place a text view UITextView *textView = [[UITextView alloc] initWithFrame:newTextViewRect textContainer:container]; [self.view addSubview:textView]; textView.translatesAutoresizingMaskIntoConstraints = NO; UILayoutGuide *safeArea = textView.superview.safeAreaLayoutGuide; [textView.leadingAnchor constraintEqualToAnchor:safeArea.leadingAnchor].active = YES; [textView.trailingAnchor constraintEqualToAnchor:safeArea.trailingAnchor].active = YES; [textView.topAnchor constraintEqualToAnchor:safeArea.topAnchor].active = YES; [textView.bottomAnchor constraintEqualToAnchor:textView.superview.bottomAnchor].active = YES; } @end
Posted Last updated
.
Post not yet marked as solved
4 Replies
1.2k Views
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
Posted Last updated
.
Post not yet marked as solved
0 Replies
196 Views
I've been struggling with this for a while now. To start off with, I am using a custom NSTextLocation as the element range on a NSTextParagraph. NSTextParagraph is a subclass of NSTextElement and both have limited public elements that can be set. From what I can see, the only thing we can set on NSTextParagraph is an NSAttributedString. It has a few other elements it inherits from NSTextElement - primarily textContentManager and elementRange. Seems simple enough...but, there is obviously something wrong with what I am doing. If we use NSTextStorage as our backing store, then the text ranges it uses is a private type NSCountableTextLocation. Being a private type, I imagine we have to implement our own NSTextLocation to use in the ranges that are set on the NSTextElement. But, for some reason, when I have multiple text elements, TextKit compares my custom NSTextLocation against a NSCountableTextLocation and crashes. -[NSCountableTextLocation compare:] receiving unmatching type (3, 1) (3,1) here being the debug description text of my custom text location (represented by line number and column). I am at a complete loss as to why this is being triggered. I have implemented isEqual, Comparable and compare: on my custom implementation. Going with the following Text """ First Second T """ I create a NSTextParagraph for each paragraph. So, I have elements 'First\n' - with range (1,1) - (2,1) - length of this range is 6 '\n' - with range (2,1) - (3,1) - length of this range is 1 'Second\n' with range (3,1) - (4,1) with length being 7 '\n' - range (4,1) - (5,1) with length 1 'T' - range (5,1) - (5,2) length 1 This bit renders and lays out fine. If I append a character at the last location, I get the exception and crash. """ First Second Th """ Exception being -[NSCountableTextLocation compare:] receiving unmatching type (3, 1) The number of text elements and the equivalent lengths match what I would get with NSTextStorage (executed in a different playground). There are a few subtle differences I see when debugging with lldb. When I use NSTextStorage, the value of _attributedString on NSTextParagraph of the first text element (and all) is "First\n\nSecond\n\nTh" - essentially the full text. The value of attributedString is "First\n" essentially the range of text that matches the paragraph. The generated text layout fragment has the correct line fragment "First\n" and not the full text. I don't see a public way to setup the NSTextParagraph this way (nor am I able to do it correctly using setValue:forKey either). I don't know if this difference really matters. So, my questions really are Does NSTextParagraph support custom NSTextLocation? If so, how do we set it up correctly? If not, do we subclass NSTextElement and do the layout ourselves? I have a full minimal project that reproduces this at https://github.com/georgemp/TextLocationCrash but it's a bit of an involved read :-) I have also filed reports using Feedback Assistant FB13547274 P.S. This crash only seems to occur on Sonoma (not on Monterrey or Ventura) P.P.S - Thank you for getting this far in this lengthy read :-)
Posted
by georgemp.
Last updated
.
Post not yet marked as solved
0 Replies
220 Views
This WWDC video (https://developer.apple.com/videos/play/wwdc2022/10090/) shows using NSTextList in TextKit 2 on both iOS and macOS to create bulleted lists in UITextView and NSTextView, respectively. The sample code still works on iOS, but doesn't work on macOS Here's a screenshot from the wwdc talk: Here's the same sample code running on macOS 14.3: Anyone know how to make NSTextList work on macOS 14? Is this a regression, did something change with the api, or has the functionality been removed?
Posted Last updated
.
Post not yet marked as solved
2 Replies
299 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 Last updated
.
Post marked as solved
1 Replies
300 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 tritonus.
Last updated
.
Post marked as solved
1 Replies
412 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 dsc4.
Last updated
.
Post not yet marked as solved
0 Replies
330 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 Last updated
.
Post not yet marked as solved
1 Replies
354 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 scalo.
Last updated
.
Post not yet marked as solved
1 Replies
339 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 Last updated
.
Post not yet marked as solved
5 Replies
469 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 JWWalker.
Last updated
.
Post not yet marked as solved
0 Replies
316 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 georgemp.
Last updated
.
Post not yet marked as solved
1 Replies
422 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 olopul.
Last updated
.
Post not yet marked as solved
1 Replies
377 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 eblu.
Last updated
.
Post not yet marked as solved
0 Replies
290 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 georgemp.
Last updated
.
Post not yet marked as solved
0 Replies
264 Views
NSMutableAttributedString append NSTextAttachment , and set paragraphStyle.alignment = .justified, will Show Overlap,Word overlap。
Posted
by yangggggg.
Last updated
.
Post not yet marked as solved
2 Replies
407 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 laishere.
Last updated
.