macOS 14.x.x下UITextView(Mac Catalyst)选中文本替换Bug求助
macOS 14.x.x + TextKit1 下 Mac Catalyst UITextView 多行选中文本替换Bug分析与修复
Bug现象
从macOS 14.x.x版本起,使用TextKit1的Mac Catalyst环境中,UITextView在选中多行文本时会触发异常:选中行内的部分文本会被选中区域内的另一部分文本意外替换。
复现步骤
- 在UITextView中填入以下文本:
Carnaroli, Maratelli, or Vialone Nano are best Vialone Nano cooks quickly – watch it! It also absorbs condiments nicely. Avoid Baldo, Originario, Ribe and Roma
- 使用光标拖拽或Shift+箭头键选中上述三行文本,即可观察到文本被替换的异常。
- 按Command-Z撤销后,可重复触发该Bug。
Bug细节
- 具体替换示例:第二行的「e Nano cooks quickly – 」会被替换为第三行的「Baldo, Originario, Ribe」
- 触发场景具有特定性,并非所有字符串或选中操作都会出现,但符合触发条件时可稳定复现
- iOS原生环境无此问题,剪贴板内容不影响Bug触发
- 多次触发后可能会自行停止出现
Bug表现截图
- 选中前:三行文本完整显示,无异常
- 选中后:第二行部分文本被第三行内容替换,显示异常
复现代码
自定义TextKit组件的复现代码
@interface TestNoteViewController () @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; // 创建布局管理器 NSLayoutManager *layoutManager = [NSLayoutManager new]; [textStorage addLayoutManager:layoutManager]; // 创建文本容器 NSTextContainer *container = [[NSTextContainer alloc] initWithSize:CGSizeMake(newTextViewRect.size.width, CGFLOAT_MAX)]; [layoutManager addTextContainer:container]; // 创建并添加文本视图 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
无需自定义TextKit组件的复现代码(同样触发Bug)
@interface TestNoteViewController () @end @implementation TestNoteViewController - (void)viewDidLoad { [super viewDidLoad]; [self createTextView]; } - (void)createTextView { UITextView *textView = [UITextView new]; textView.text = self.note.text; [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
原因分析
这是macOS 14.x.x版本中Mac Catalyst对TextKit1的适配Bug,核心问题在于UITextView处理多行选中时,NSTextStorage的范围计算出现错误,导致布局管理器(NSLayoutManager)同步选中状态与文本内容时,混淆了不同位置的文本索引,触发了意外的文本覆盖操作。
修复方案
- 升级到TextKit2:TextKit2是苹果新一代文本框架,在macOS 14及后续版本中对Mac Catalyst的支持更完善,替换TextKit1组件即可规避该Bug。需将
NSTextStorage、NSLayoutManager、NSTextContainer替换为NSTextContentStorage、NSTextLayoutManager、NSTextContainer,同时调整UITextView的初始化逻辑。 - 临时规避方案:若无法立即升级TextKit2,可通过监听UITextView的选中状态变化,手动校验文本完整性:实现
UITextViewDelegate的textViewDidChangeSelection:方法,在选中操作后对比当前文本与原始文本,若发现异常则恢复原始内容。 - 等待系统补丁:该Bug属于系统级适配问题,苹果大概率会在后续macOS更新中修复,可关注系统更新并及时升级。
内容的提问来源于stack exchange,提问作者AVS
相关产品推荐
相关产品推荐

