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

Posts under TextKit tag

53 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

NSLayoutManager returning inconsistent values for a glyph's text container and its line fragment rect
TLDR: NSLayoutManager's textContainer(forGlyphAt:effectiveRange:) and lineFragmentRect(forGlyphRange:effectiveRange:) are returning inconsistent results. Context: I'm developing a word processing app that paginates from an NSTextStorage using NSLayoutManager. My app uses a text attribute (.columnType) to paginate sub-ranges of the text at a time, ensuring that each columnRange gets a container (or series of containers across page breaks) to fit. This is to support both multi-column and standard full-page-width content. After any user edit, I update pagination data in my Paginator model class. I calcuate frames/sizes for the views/containers, along with what superview they belong to (page). The UI updates accordingly. In order to determine whether the columnRange has overflowed from a container due to a page break OR whether the range of text hasn't overflowed its container and is actually using less space than available and should be sized down, I call both: layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer)` // and `layoutManager.lineFragmentRect(forGlyphAt: lastGlyphOfColumn, effectiveRange: nil) Apple Documentation notes that both these calls force glyph generation and layout. As I'm in early development, I have not set non-contiguous layout. So these should be causing full layout, assuring accurate return values. Or so I'd hoped. This does work fine in many cases. I edit. Pagination works. But then I'll encounter UI-breaking inconsistent returns from these two calls. By inconsistent, I mean that the second call returns a line fragment rect that is in the container coordinates of A DIFFERENT container than the container returned by the first call. To be specific, the line fragment rect seems to be in the coordinates of the container that comes next in layoutManager.textContainers. Example Code: if !layoutManager.textContainers.indices.contains(i) { containerToUse = createTextContainer(with: availableSize) layoutManager.addTextContainer(containerToUse) } else { // We have a container already but it may be // the wrong size. containerToUse = layoutManager.textContainers[i] if containerToUse.size.width != availableSize.width { // Mandatory that we resize if we don't have // a matching width. Height resizing is not // mandatory and requires a layout check below. containerToUse.size = availableSize } } let glyphRange = layoutManager.glyphRange(forCharacterRange: remainingColumnRange, actualCharacterRange: nil) let lastGlyphOfColumn = NSMaxRange(glyphRange) - 1 var containerForLastGlyphOfColumn = layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer) if containerForLastGlyphOfColumn != containerToUse && containerToUse.size.height < availableSize.height { // If we are here, we overflowed the container, // BUT the container we overflowed didn't use // the maximum remaining page space (this // means it was a pre-existing container that // needs to be sized up and checked once more). // NOTE RE: THE BUG: // at this point, prints show... // containerToUse.size.height // =628 // availableSize.height // =648 containerToUse.size = availableSize containerForLastGlyphOfColumn = layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer) } // We now check again, knowing that the container we // are testing flow into is the max size it can be. if containerForLastGlyphOfColumn != containerToUse { // If we are here, we have overflowed the // container, so containerToUse size SHOULD be // final/accurate, since it is fully used. actualCharRangeInContainer = layoutManager.characterRange(forGlyphRange: actualGlyphRangeInContainer, actualGlyphRange: nil) // Start of overflow range is the first character // in the container that was overflowed into. let overflowLoc = actualCharRangeInContainer.location remainingColumnRange = NSRange(location: overflowLoc, length: remainingColumnRange.length - overflowLoc) // Update page count as we have broken to a new page currentPage += 1 } else { // If we are here, we have NOT overflowed // from the container. BUT... // THE BUG: // ***** HERE IS THE BUG! ***** lineFragmentRectForLastChar = layoutManager.lineFragmentRect(forGlyphAt: lastGlyphOfColumn, effectiveRange: nil) let usedHeight = lineFragmentRectForLastChar.maxY // BUG: ^The lines of code above return a // fragment rect that is in the coordinates // of the WRONG text container. Prints show: // usedHeight // =14 // usedHeight shouldn't be just 14 if this is // the SAME container that, when it was 628 // high, resulted in text overflowing. // Therefore, the line fragment here seems // to be in the coordinates of the ENSUING // container that we overflowed INTO, but // that shouldn't be possible, since we're in // a closure for which we know: // // containerForLastGlyphOfColumn == containerToUse // // If the last glyph container is the container // we just had to size UP, why does the final // glyph line fragment rect have a maxY of 14!? // Including ensuing code below only for context. if usedHeight < containerToUse.size.height { // Adjust container size down to usedRect containerToUse.size = CGSize(width: containerToUse.size.width, height: usedHeight) } else if usedHeight == availableSize.height { // We didn't force break to a new page BUT // we've used exactly the height of our page // to layout this column range, so need to // break to a new page for any ensuing text // columns. currentPage += 1 } else if usedHeight > containerToUse.size.height { // We should have caught this earlier. Text // has overflowed, but this should've been // caught when we checked // containerForLastGlyphOfColumn != // containerToUse. // // Note: this error has never thrown. throw PaginationError.unknownError("Oops.") } } Per my comments in the code block above, I don't understand why the very same text container that just overflowed and so had to be sized up from 628 to 648 in order to try to fit a glyph would now report that same glyph as both being IN that same container and having a line fragment rect with a maxY of just 14. A glyph couldn't fit in a container when it was 628 high, but if I size it up to 648, it only needs 14? There's something very weird going on here. Working with NSLayoutManager is a bit of a nightmare given the unclear documentation. Any help or insight here would be massively, massively appreciated.
1
0
215
6d
NSLayoutManager laying out overlapping text into the same NSTextContainer even when there are more containers available.
In summation: I have a nasty bug where my layout manager is laying out text visually overlapping on top of other text, i.e., into a container that it should have left in the rear view as it continues to lay out into ensuing containers. Details below... I'm coding a word processing app with some custom pagination that involves multiple pages, within which there can be multiple NSTextView/NSTextContainer pairs that represent single column or dual column runs of text. I generate pagination data by using a measuring NSLayoutManager. This process ensures that no containers overlap, and that they are sized correctly for their associated ranges of text (i.e., non-overlapping, continuous ranges from a single NSTextStorage). I determine frame sizes by a series of checks, most importantly, by finding the last glyph in a column. Prior to the code below, remainingColumnRange represents the remaining range of my textStorage that is of a consistent column type (i.e., single, left column, or right column). My measuring passes consist of my measuringLayoutManager laying out text into its textContainers, the final of which is an extra overflowContainer (i.e., == measuringLayoutManager.textContainers.last!) which I only use to find the last glyph in the second to last container (measuringContainer, which is thus == measuringLayoutManager.textContainers[count - 2]) let glyphRangeOfLastColumnChar = measuringLayoutManager.glyphRange(forCharacterRange: remainingColumnRange, actualCharacterRange: nil) let lastGlyphIndex = NSMaxRange(glyphRangeOfLastColumnChar) - 1 measuringLayoutManager.ensureLayout(for: measuringContainer) // Not sure if this is necessary, but I've added it to insure I'm getting accurate measurements. if measuringLayoutManager.textContainer(forGlyphAt: lastGlyphOfColumnIndex, effectiveRange: &actualGlyphRangeInContainer) == overflowContainer { actualCharRangeInContainer = measuringLayoutManager.characterRange(forGlyphRange: actualGlyphRangeInContainer, actualGlyphRange: nil) let overflowLoc = actualCharRangeInContainer.location remainingColumnRange = NSRange(location: overflowLoc, length: remainingColumnRange.length - overflowLoc) currentPage += 1 } else { lineFragmentRectForLastChar = measuringLayoutManager.lineFragmentRect(forGlyphAt: lastGlyphIndex, effectiveRange: nil) // Resize measuring container if needed. let usedHeight = lineFragmentRectForLastChar.maxY if usedHeight < measuringContainer.size.height { measuringContainer.size = CGSize(width: measuringContainer.size.width, height: usedHeight) } else if usedHeight == measuringContainer.size.height { currentPage += 1 // we perfectly filled the page } else { // This would be an error case, because all cases should have been handled prior to arriving here. I throw an error. I have never fallen through here. throw MyClass.anError } } // I use the above data to create a PageLayoutItem, which is a struct that has frame data (CGRect/x,y,w,h), a containerIndex (Int), pageNumber (Int), textRange (NSRange), columnType (custom enum). // After this I remove the overflowContainer, and continue to iterate through. This is inefficient but I'm simplifying my code to identify the root issue. I don't explicitly use these containers when done with my pagination process. Rather, I use the PageLayoutItems I have created to generate/resize/remove textContainers/textViews for the UI as needed. My UI-interfacing/generating NSLayoutManager, which is of course assigned to the same NSTextStorage as the measuring layout manager, then iterates through my paginator model class' pageLayoutItems array to generate/resize/remove. I have verified my pagination data. None of my frames overlap. They are sized exactly the same as they should be per my measurement passes. The number of containers/views needed is correct. But here's the issue: My views render the text that SHOULD appear in my final textContainer/textView as visually overlapping the text in my second to last textContainer/textView. I see a garble of text. When I iterate through my UI textContainers, I get this debug print: TextContainer 0 glyphRange: {0, 172} TextContainer 1 glyphRange: {172, 55} TextContainer 2 glyphRange: {227, 100} // this is wrong, final 31 chars should be in container 3 TextContainer 3 glyphRange: {327, 0} // empty range here, odd I have tried setting textContainers for glyph ranges explicitly, via: // Variable names just for clarity here layoutManager.setTextContainer(correctTextView.textContainer!, forGlyphRange: correctGlyphRangeForThisContainer) Debug prints show that I'm setting the right ranges there. But they don't retain. I have tried resizing my final text container to be much larger in case that was the issue. No dice. My final range of text/glyphs still lays out in the wrong container and overlaps the other content laid out there. Any help here?? I've scoured the forums and have been dealing with this bug for two weeks straight with no hope in sight.
3
0
439
3w
Can I edit NSTextLineFragment position or update manually in TextKit2
In my app, User can select word in the UITextView, then I want to insert a content under the selected words(the comment words shouldn't be selected).like: I found TextKit2 only support edit NSTextParagraph position, or I missed some features in NSTextLayoutManager? I try to override the NSTextLayoutFragment and update the draw(at point: CGPoint, in context: CGContext) but I found, user still can select the origin content at the origin position, even if the layer of linefragment layer on the corrent position not the origin position.
2
0
372
3w
Genmoji is there but doesn't display in NSTextView
I have discovered an odd issue with NSTextView, NSAdaptiveImageGlyph, and NSLayoutManager. (Forgive the Objective-C... I'm old-school.) I can easily display an attributed string containing text and Genmoji NSAdaptiveImageGlyphs with something very basic like this: textView = [[NSTextView alloc] initWithFrame:NSMakeRect(100.0, 100.0, 500.0, 100.0)]; [textView.textStorage setAttributedString:sampleText]; [self addSubview:textView]; //NSLayoutManager *layoutManager = textView.layoutManager; I can even insert or paste new Genmoji into there as well. However, if I uncomment out that last line to retrieve the layoutManager of the text view it breaks the rendering of Genmoji, even if I do nothing with the layoutManager! Just getting it causes the NSTextView to skip past Genmoji glyphs when spacing out glyphs and when rendering. I thought perhaps getting the layoutManager caused an internal cache to break so I tried ensureLayoutForTextContainer but that didn't help. Interestingly if I paste a new Genmoji into the NSTextView it doesn't display either. Yet if I select all, copy, and paste into TextEdit I see all the Genmoji just fine so they are there, just invisible and zero size. Using the arrow keys I have to skip past the invisible Genmoji. But if I comment out that line again I see all Genmoji with no issues. I do need to use a layoutManager to measure things like text bounds and heights. Elsewhere in the code, my existing drawing routines would like to draw the resulting attributed string not with drawAtPoint (which works fine) but with drawGlyphsForGlyphRange but that doesn't work either as it uses NSLayoutManager. Is there a trick to getting NSAdaptiveImageGlyphs working with NSLayoutManager? Thanks for any assistance!
3
0
422
3w
TextKit 2 Background drawing incorrect
Hi I am drawing TextKit2 managed NSAttributedStrings into a NSBitmapImageRep successfully, enumerating the Text Layout Fragments is giving me bogus background drawing This is the core drawing code, its pretty simple: I manage the flipped property myself since NSTextLayoutManager assumes a flipped coordinate. if let context = NSGraphicsContext(bitmapImageRep: self.textImageRep!) { NSGraphicsContext.current = context let rect = NSRect(origin: .zero, size: self.outputSize) NSColor.clear.set() rect.fill() // Flip the context context.cgContext.saveGState() context.cgContext.translateBy(x: 0, y: self.outputSize.height) context.cgContext.scaleBy(x: 1.0, y: -1.0) let textOrigin = CGPoint(x: 0.0, y: 0.0 ) let titleRect = CGRect(origin: textOrigin, size: self.themeTextContainer.size) NSColor.orange.withAlphaComponent(1).set() titleRect.fill() self.layoutManager.enumerateTextLayoutFragments(from: nil, using: { textLayoutFragment in // Get the fragment's rendering bounds let fragmentBounds = textLayoutFragment.layoutFragmentFrame print("fragmentBounds: \(fragmentBounds)") // Render the fragment into the context textLayoutFragment.draw(at: fragmentBounds.origin, in: context.cgContext) return true }) context.cgContext.restoreGState() } NSGraphicsContext.restoreGraphicsState() I have a mutable string which has various paragraph styles which I add to the layout manager / text storage like so let titleParagraphStyle = NSMutableParagraphStyle() titleParagraphStyle.alignment = .center titleParagraphStyle.lineBreakMode = .byWordWrapping titleParagraphStyle.lineBreakStrategy = .standard var range = NSMakeRange(0, self.attributedProgrammingBlockTitle.length) self.attributedProgrammingBlockTitle.addAttribute(.foregroundColor, value: NSColor(red: 243.0/255.0, green: 97.0/255.0, blue: 97.0/255.0, alpha: 1.0), range:range) self.attributedProgrammingBlockTitle.addAttribute(.backgroundColor, value: NSColor.cyan, range:range) self.attributedProgrammingBlockTitle.addAttribute(.font, value: NSFont.systemFont(ofSize: 64), range:range) self.attributedProgrammingBlockTitle.addAttribute(.paragraphStyle, value:titleParagraphStyle, range:range) range = NSMakeRange(0, self.attributedThemeTitle.length) self.attributedThemeTitle.addAttribute(.foregroundColor, value: NSColor.white, range:range ) self.attributedThemeTitle.addAttribute(.backgroundColor, value: NSColor.purple, range:range) self.attributedThemeTitle.addAttribute(.font, value: NSFont.systemFont(ofSize: 48), range:range) self.attributedThemeTitle.addAttribute(.paragraphStyle, value:NSParagraphStyle.default, range:range) range = NSMakeRange(0, self.attributedText.length) self.attributedText.addAttribute(.foregroundColor, value: NSColor.white, range:range ) self.attributedText.addAttribute(.backgroundColor, value: NSColor.yellow, range:range) self.attributedText.addAttribute(.font, value: NSFont.systemFont(ofSize: 36), range:range) self.attributedText.addAttribute(.paragraphStyle, value:NSParagraphStyle.default, range:range) let allText = NSMutableAttributedString() allText.append(self.attributedProgrammingBlockTitle) allText.append(NSAttributedString(string: "\n\r")) allText.append(self.attributedThemeTitle) allText.append(NSAttributedString(string: "\n\r")) allText.append(self.attributedText) self.textStorage.textStorage?.beginEditing() self.textStorage.textStorage?.setAttributedString(allText) self.textStorage.textStorage?.endEditing() self.layoutManager.ensureLayout(for: self.layoutManager.documentRange) however, i get incorrect drawing for the background color font attributes. Its origin is zero, and not correctly aligned at all with the text. How can I get correct rendering of backgrounds from TextKit2? Here is an image of my output:
3
0
481
3w
Unexpected Insertion of U+2004 (Space) When Using UITextView with Pinyin Input on iOS 18
I encountered an issue with UITextView on iOS 18 where, when typing Pinyin, extra Unicode characters such as U+2004 are inserted unexpectedly. This occurs when using a Chinese input method. Steps to Reproduce: 1. Set up a UITextView with a standard delegate implementation. 2. Use a Pinyin input method to type the character “ㄨ”. 3. Observe that after the character “ㄨ” is typed, extra spaces (U+2004) are inserted automatically between the characters. Code Example: class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } } extension ViewController: UITextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { print("shouldChangeTextIn: range \(range)") print("shouldChangeTextIn: replacementText \(text)") return true } func textViewDidChange(_ textView: UITextView) { let currentText = textView.text ?? "" let unicodeValues = currentText.unicodeScalars.map { String(format: "U+%04X", $0.value) }.joined(separator: " ") print("textViewDidChange: textView.text: \(currentText)") print("textViewDidChange: Unicode Scalars: \(unicodeValues)") } } Output: shouldChangeTextIn: range {0, 0} shouldChangeTextIn: replacementText ㄨ textViewDidChange: textView.text: ㄨ textViewDidChange: Unicode Scalars: U+3128 ------------------------ shouldChangeTextIn: range {1, 0} shouldChangeTextIn: replacementText ㄨ textViewDidChange: textView.text: ㄨ ㄨ textViewDidChange: Unicode Scalars: U+3128 U+2004 U+3128 ------------------------ shouldChangeTextIn: range {3, 0} shouldChangeTextIn: replacementText ㄨ textViewDidChange: textView.text: ㄨ ㄨ ㄨ textViewDidChange: Unicode Scalars: U+3128 U+2004 U+3128 U+2004 U+3128 This issue may affect text processing, especially in cases where precise text manipulation is required, such as calculating ranges in shouldChangeTextIn.
5
0
579
4w
Textview/textfield crash by undoManager
Fatal Exception: NSInternalInconsistencyException 0 CoreFoundation 0x2d5ec __exceptionPreprocess 1 libobjc.A.dylib 0x31244 objc_exception_throw 2 Foundation 0x8b58b8 -[NSUndoManager endUndoGrouping] 3 Foundation 0x279154 __NSFirePerformWithOrder 4 CoreFoundation 0x21894 CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION 5 CoreFoundation 0x213e8 __CFRunLoopDoObservers 6 CoreFoundation 0x75cf8 __CFRunLoopRun 7 CoreFoundation 0xc8274 CFRunLoopRunSpecific 8 GraphicsServices 0x14c0 GSEventRunModal 9 UIKitCore 0x3ee77c -[UIApplication _run] 10 UIKitCore 0x14e64 UIApplicationMain 11 Glip 0x70398 main + 13 (main.swift:13) 12 ??? 0x1c060cde8 (Missing)
2
0
459
Dec ’24
IOS 18 uses TextKit to calculate the height of attributed strings, but the calculation is inaccurate.
In iOS 18, using TextKit to calculate the height of attributed strings is inaccurate. The same method produces correct results in systems below iOS 18. - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(20, 40, 100, 0)]; textView.editable = NO; textView.scrollEnabled = NO; textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0); textView.textContainer.lineFragmentPadding = 0; textView.backgroundColor = [UIColor lightGrayColor]; [self.view addSubview:textView]; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"陈家坝好吃的撒海程邦达不差大撒把传达是吧才打卡吃吧金卡多措并举哈不好吃大杯茶十八次是吧"]; NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; paragraphStyle.lineSpacing = 4; [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, attributedString.length)]; [attributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16] range:NSMakeRange(0, attributedString.length)]; [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, attributedString.length)]; textView.attributedText = attributedString; CGFloat height = [self test:attributedString]; textView.frame = CGRectMake(20, 40, 100, height); } - (CGFloat)test:(NSAttributedString *)attString { NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attString]; NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; [textStorage addLayoutManager:layoutManager]; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(100, CGFLOAT_MAX)]; textContainer.lineFragmentPadding = 0; [layoutManager addTextContainer:textContainer]; [layoutManager ensureLayoutForTextContainer:textContainer]; CGFloat height = [layoutManager usedRectForTextContainer:textContainer].size.height; return ceil(height); }
2
0
589
Dec ’24
IOS 18 uses TextKit to calculate the height of attributed strings, but the calculation is inaccurate.
In iOS 18, using TextKit to calculate the height of attributed strings is inaccurate. The same method produces correct results in systems below iOS 18. - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(20, 40, 100, 0)]; textView.editable = NO; textView.scrollEnabled = NO; textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0); textView.textContainer.lineFragmentPadding = 0; textView.backgroundColor = [UIColor lightGrayColor]; [self.view addSubview:textView]; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"陈家坝好吃的撒海程邦达不差大撒把传达是吧才打卡吃吧金卡多措并举哈不好吃大杯茶十八次是吧"]; NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; paragraphStyle.lineSpacing = 4; [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, attributedString.length)]; [attributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16] range:NSMakeRange(0, attributedString.length)]; [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, attributedString.length)]; textView.attributedText = attributedString; CGFloat height = [self test:attributedString]; textView.frame = CGRectMake(20, 40, 100, height); } - (CGFloat)test:(NSAttributedString *)attString { // 创建 NSTextStorage 并设定文本内容 NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attString]; // 创建 NSLayoutManager 并关联 NSTextStorage NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; [textStorage addLayoutManager:layoutManager]; // 创建 NSTextContainer 并设定其属性 NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(100, CGFLOAT_MAX)]; textContainer.lineFragmentPadding = 0; [layoutManager addTextContainer:textContainer]; // 强制布局管理器计算布局 [layoutManager ensureLayoutForTextContainer:textContainer]; // 获取文本内容所占的高度 CGFloat height = [layoutManager usedRectForTextContainer:textContainer].size.height; // 返回四舍五入高度 return ceil(height); }
1
0
441
Dec ’24
TextKit2 : - The text inserted between the attributedText(Paragraph) doesn't inherit the attributes of existing text
I have added an custom attribute for a paragraph using the below method textStorage.addAttribute(.customCase, value: "checkList", range: paragraphRange) When I insert some text in between the text which contains the custom attribute, that text is not inheriting/propagating the custom attribute of existing paragraph text Old Text : - This is a test New Text : - This is "some new" a test The inserted part is not getting the custom attribute of the old text, Can I know why it's happening, Is it some textKit2's behaviour.
0
0
262
Dec ’24
How to create and manage nested List with NSTextList, NSAttributedString and UI/NSTextView
I am developing a library for RichTextEditor for SwiftUI, and I am facing issues with implementing NSParagraphStyle related features like nested bullet lists and text alignment. I have searched a lot and personally feel that the documentation is not enough on this topic, so here I want to discuss how we can achieve the nested list with UI/NSTextView and natively available NSTextList in NSParagraphStyle.textLists. The problem is I am not able to understand how I can use this text list and how to manage adding list and removing list with my editor I have seen code that work adding attributes to each string and then merge them, but I don't want that, I want to add/update/remove attributes from selected text and if text is not selected then want to manage typing attributes to keep applied attributes to current position
1
0
327
Dec ’24
TextKit 2 : replaceContents(in:with:) is not working
I have NsTextList and it has [NsTextListElement], I want to replace an NsTextListElement with other element like NsTextParagraph or NstextListElement or an AttributedString. For some reason the below method is not working at all. And I couldn't find any alternate way of replacing the elements textLayoutManager.replaceContents(in: element.elementRange, with: NSAttributedString(string: "happy"))
2
0
427
Dec ’24
How the input of UITextField is stored ?
When user enters in a textfield, is the input of textfield gets stored in a String ? If yes, then String in swift being immutable, as user keeps on typing does new memory for storing that text gets allocated with each key stroke ? And when we read users input by using delegate method textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) from textfield.text, we get users input in a String. Is it same storage as used by textfield for storing the user input on key stroke or is it some other storage with copy of the user's input in it? Or is UItextfield using a diffrent data structure (buffer) for storing the user input and when we do textfield.text, it gives a copy of data stored in original buffer?
1
0
449
Nov ’24
Inconsistent "New York" font returned between devices
I'm seeing a discrepancy in the metrics of the "New York" system font returned from various Macs. Here's a sample (works well in Playgrounds): import Cocoa let font = NSFont(descriptor: .preferredFontDescriptor(forTextStyle: .body).withDesign(.serif)!, size: NSFont.systemFontSize)! print("\(font.fontName) \(font.pointSize)") print("ascender: \(font.ascender)") let layoutManager = NSLayoutManager() print("lineHeight: \(layoutManager.defaultLineHeight(for: font))") When I run this on multiple Macs, I get two types of different results. Some – most Macs – report this: .NewYork-Regular 13.0 ascender: 12.3779296875 lineHeight: 16.0 However, when I run on my own Mac (and also on the one of a colleague), I get this instead: .NewYork-Regular 13.0 ascender: 14.034145955454255 lineHeight: 19.0 It's clearly the same font in the same point size. Yet the font has different metrics, causing a layout manager to also compute a significantly different line height. So far I've found out that neither CPU generation/architecture nor macOS version seem to play a role. This issue has been reproducible since at least macOS 14. Having just migrated to a new Mac, the issue is still present. This does not affect any other system or commonly installed font. It's only New York (aka the serif design). So I assume this must be something with my setup. Yet I have been unable to find anything that may cause this. Anybody have some ideas? Happy to file a bug report but wanted to check here first.
2
0
540
Dec ’24
Show new Format Panel on button press
I'm working on integrating the new format panel shown in the WWDC24 session "What's New in UIKit" under the Text Improvements section. So far, I've implemented long-press functionality on a text passage, allowing the editing options to appear. From there, you can go to Format > More..., which successfully opens the new format panel. However, I would also like to add a button to programmatically display this format panel—similar to how the Apple Notes app has a button in the keyboard toolbar to open it. Does anyone know how to achieve this? Here's my current code for the text editor (I've enabled text formatting by setting allowsEditingTextAttributes to true): struct TextEditorView: UIViewRepresentable { @Binding var text: String func makeCoordinator() -> Coordinator { Coordinator(self) } func makeUIView(context: Context) -> UITextView { let textEditorView = UITextView() textEditorView.delegate = context.coordinator textEditorView.allowsEditingTextAttributes = true return textEditorView } func updateUIView(_ uiView: UITextView, context: Context) { uiView.text = text } class Coordinator: NSObject, UITextViewDelegate { var parent: TextEditorView init(_ uiTextView: TextEditorView) { self.parent = uiTextView } func textViewDidChange(_ textView: UITextView) { self.parent.text = textView.text } } } Thanks in advance for any guidance!
0
0
318
Nov ’24
allowedWritingToolsResultOptions has no effect in iOS 18.1 Writing Tools
The UITextView.allowedWritingToolsResultOptions has no effect to how "Writing Tools" feature works. When it is set to empty, it still offer all options in the Writing Tools popup dialog. The result is that it is not possible to limit output results to eg. only plain text, or disable tables in output. let textView = UITextView() textView.isEditable = true textView.writingToolsBehavior = .complete textView.allowedWritingToolsResultOptions = [] resulting Writing Tools has all options available. I Tested with TextKit1 and TextKit 2 setup. tested on iPadOS 18.1 beta (22B5069a) Report: FB15429824
2
0
709
Nov ’24
Tapping a TextKit 2 backed UITextView moves the caret to a random location
With the upcoming launch of Apple Intelligence and Writing Tools, we've been forced to migrate our text editing app to TextKit 2. As soon as we released the update, we immediately got complaints about incorrect selection behaviour, where the user would tap a word to begin editing, but the caret would be shown in an undefined location, often dozens of paragraphs below the selected content. To reproduce: Create a UITextView backed by a standard TextKit 2 stack and a large amount of text (50,000+ words) - see sample project below Scroll quickly through the text view (at least 20% of the way down) Tap once to select a position in the document. Expected: The caret appears at the location the user tapped, and UITextView.selectedRange is the range of the text at the location of the tap. This is the behaviour of TextKit 1 based UITextViews. Actual: The caret is positioned at an undefined location (often completely off screen), and the selectedRange is different to the range at the location of the tap, often by several thousand. There is no pattern to the magnitude of the discrepancy. This incorrect behaviour occurs consistently in the sample project on the simulator, but you may need to hide the keyboard by pulling down, then repeat steps 2-3 a few times. This happens on iPhone and iPad, and on iOS 17, 18, and 18.1. Do you have any insight into why this might be happening or how to work around this issue? Sample code is here: https://github.com/nathantesler/textkit2-issue/tree/master
2
0
573
Sep ’24