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

Posts under TextKit tag

176 Posts

Post

Replies

Boosts

Views

Activity

TextKit2: Relation between NSRange and NSTextRange
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
4
0
2.6k
Mar ’24
How do we use a custom NSTextContentManager with NSTextParagraph
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 :-)
0
4
1.1k
Feb ’24
NSTextList support in macOS 14
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?
2
3
1.1k
Feb ’24
NSTextField problem in XCode 15.2
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?
2
0
1k
Feb ’24
Avoiding rasterizing UITextView when exporting to PDF
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?
2
0
1.1k
Jan ’24
HTML NSAttributedString technique that avoids Sonoma's TextKit issues
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):
1
0
1.1k
Jan ’24
UITextView giving wrong UITextPosition with closestPosition on iOS 17 with custom arabic font
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.
0
0
854
Jan ’24
TextKit 2: edits make duplicate text elements
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) }
1
1
954
Jan ’24
UITextView bacground color changed
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.
1
0
837
Dec ’23
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
1.5k
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
846
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
755
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
872
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
891
Nov ’23
How to override NSTextLayoutFragment.textLineFragments properly?
I am trying to add custom attributes on-the-fly. To make it work, I subclassed NSTextLayoutFragment, and overrode .textLineFragments property to return custom made NSTextLineFragment objects. But if I override it, TextKit2 no longer render the text and selection also doesn't work. It's same even if provide NSTextLineFragment with exactly same properties (attributed string and range). In WWDC 22 video, you told me that NSTextLayoutFragment and NSTextLineFragment are all immutable and have value semantics. But it doesn't work with different object, therefore seems still have very strong reference semantics. How to make it work with custom attributes? P.S. I also reported this as a feedback -- https://feedbackassistant.apple.com/feedback/12443016
1
1
1.3k
Oct ’23
Crash while rendering glyphs in iOS 14
I've a custom UIView to render a large piece of text using CATiledLayers. My draw(rect:) implementation is quite simple: override func draw(_ rect: CGRect) { &#9;let range = layoutManager.glyphRange(forBoundingRect: rect, in: textContainer) &#9;layoutManager.drawBackground(forGlyphRange: range, at: .zero) &#9;layoutManager.drawGlyphs(forGlyphRange: range, at: .zero) } This code works without any issue on iOS 13, but fails with a crash on iOS 14 simulator: Thread 9: EXC_BAD_ACCESS (code=1, address=0x28) #0 0x00007fff2396a3f5 in _NSLayoutTreeMoveToGlyphIndex () I can see that draw(rect:) is being called from different threads in both iOS 13 and 14. However the EXC_BAD_ACCESS has never happened so far in iOS 13. I wonder whether this is a behaviour change in iOS 14 or an issue. If this is a change in NSLayoutManager, then is it still possible to use CATiledLayer to render large amount of text in coordination with NSLayoutManager?
1
0
1.1k
Oct ’23
TextKit2: Relation between NSRange and NSTextRange
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
Replies
4
Boosts
0
Views
2.6k
Activity
Mar ’24
How do we use a custom NSTextContentManager with NSTextParagraph
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 :-)
Replies
0
Boosts
4
Views
1.1k
Activity
Feb ’24
NSTextList support in macOS 14
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?
Replies
2
Boosts
3
Views
1.1k
Activity
Feb ’24
NSTextField problem in XCode 15.2
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?
Replies
2
Boosts
0
Views
1k
Activity
Feb ’24
Avoiding rasterizing UITextView when exporting to PDF
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?
Replies
2
Boosts
0
Views
1.1k
Activity
Jan ’24
HTML NSAttributedString technique that avoids Sonoma's TextKit issues
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):
Replies
1
Boosts
0
Views
1.1k
Activity
Jan ’24
UITextView giving wrong UITextPosition with closestPosition on iOS 17 with custom arabic font
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.
Replies
0
Boosts
0
Views
854
Activity
Jan ’24
TextKit 2: edits make duplicate text elements
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) }
Replies
1
Boosts
1
Views
954
Activity
Jan ’24
UITextView bacground color changed
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.
Replies
1
Boosts
0
Views
837
Activity
Dec ’23
How to create paginated PDF from NSTextView?
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?
Replies
5
Boosts
0
Views
1.1k
Activity
Dec ’23
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.
Replies
1
Boosts
0
Views
1.5k
Activity
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.
Replies
1
Boosts
0
Views
846
Activity
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
Replies
0
Boosts
0
Views
755
Activity
Nov ’23
iOS 17.1.1 NSMutableAttributedString show
NSMutableAttributedString append NSTextAttachment , and set paragraphStyle.alignment = .justified, will Show Overlap,Word overlap。
Replies
0
Boosts
0
Views
523
Activity
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.
Replies
2
Boosts
0
Views
872
Activity
Nov ’23
Same issue here.
The crash happens in: iOS 16 - 96% iOS 17 - 3% iPad OS 16 - 1%
Replies
1
Boosts
0
Views
639
Activity
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?
Replies
2
Boosts
0
Views
891
Activity
Nov ’23
How to override NSTextLayoutFragment.textLineFragments properly?
I am trying to add custom attributes on-the-fly. To make it work, I subclassed NSTextLayoutFragment, and overrode .textLineFragments property to return custom made NSTextLineFragment objects. But if I override it, TextKit2 no longer render the text and selection also doesn't work. It's same even if provide NSTextLineFragment with exactly same properties (attributed string and range). In WWDC 22 video, you told me that NSTextLayoutFragment and NSTextLineFragment are all immutable and have value semantics. But it doesn't work with different object, therefore seems still have very strong reference semantics. How to make it work with custom attributes? P.S. I also reported this as a feedback -- https://feedbackassistant.apple.com/feedback/12443016
Replies
1
Boosts
1
Views
1.3k
Activity
Oct ’23
NSTextView find and replace custom dialog
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?
Replies
1
Boosts
0
Views
780
Activity
Oct ’23
Crash while rendering glyphs in iOS 14
I've a custom UIView to render a large piece of text using CATiledLayers. My draw(rect:) implementation is quite simple: override func draw(_ rect: CGRect) { &#9;let range = layoutManager.glyphRange(forBoundingRect: rect, in: textContainer) &#9;layoutManager.drawBackground(forGlyphRange: range, at: .zero) &#9;layoutManager.drawGlyphs(forGlyphRange: range, at: .zero) } This code works without any issue on iOS 13, but fails with a crash on iOS 14 simulator: Thread 9: EXC_BAD_ACCESS (code=1, address=0x28) #0 0x00007fff2396a3f5 in _NSLayoutTreeMoveToGlyphIndex () I can see that draw(rect:) is being called from different threads in both iOS 13 and 14. However the EXC_BAD_ACCESS has never happened so far in iOS 13. I wonder whether this is a behaviour change in iOS 14 or an issue. If this is a change in NSLayoutManager, then is it still possible to use CATiledLayer to render large amount of text in coordination with NSLayoutManager?
Replies
1
Boosts
0
Views
1.1k
Activity
Oct ’23