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);
}
TextKit
RSS for tagManage text storage and perform custom layout of text-based content in your app's views using TextKit.
Posts under TextKit tag
50 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
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)
I have an app that uses UITextView for some text editing. I have some custom operations I can do on the text that I want to be able to undo, and I'm representing those operations in a way that plugs into NSUndoManager nicely. For example, if I have a button that appends an emoji to the text, it looks something like this:
func addEmoji() {
let inserting = NSAttributedString(string: "😀")
self.textStorage.append(inserting)
let len = inserting.length
let range = NSRange(location: self.textStorage.length - len, length: len)
self.undoManager?.registerUndo(withTarget: self, handler: { view in
view.textStorage.deleteCharacters(in: range)
}
}
My goal is something like this:
Type some text
Press the emoji button to add the emoji
Trigger undo (via gesture or keyboard shortcut) and the emoji is removed
Trigger undo again and the typing from step 1 is reversed
If I just type and then trigger undo, the typing is reversed as you'd expect. And if I just add the emoji and trigger undo, the emoji is removed. But if I do the sequence above, step 3 works but step 4 doesn't. The emoji is removed but the typing isn't reversed.
Notably, if step 3 only changes attributes of the text, like applying a strikethrough to a selection, then the full undo chain works. I can type, apply strikethrough, undo strikethrough, and undo typing.
It's almost as if changing the text invalidates the undo manager's previous operations?
How do I insert my own changes into UITextView's NSUndoManager without invalidating its chain of other operations?
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.
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
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"))
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.
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?
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
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!