游乐游手机版
首页/编程语言/文章详情

iOS13三指撤销与文案限长适配实例详解

时间:2026-06-14 06:48
iOS13适配中,三指撤销操作触发UITextField文案限长回调时,同步截断文本会破坏撤销栈状态导致崩溃。解决方案:将截断逻辑延迟到下一个runloop执行,数字键盘场景则在代理中直接拒绝超长输入,从源头避免崩溃。

正文

在适配iOS13时,遇到一个挺棘手的问题:UITextField输入中文时,三指撤销竟然直接crash了。如果你也在做iOS13适配,很可能碰上这个坑。

iOS13适配三指撤销和文案限长实例详解

Bugly报错

NSInternalInconsistencyException
setGroupIdentifier:: _NSUndoStack 0x1206532f0 is in invalid state, calling setGroupIdentifier with no begin group mark

堆栈信息

从堆栈信息能很清楚地看到,crash发生在撤销操作链中,核心是_NSUndoStack的状态异常。

 CoreFoundation	___exceptionPreprocess + 220
 libobjc.A.dylib	objc_exception_throw + 56
 Foundation	-[_NSUndoStack groupIdentifier]
 Foundation	-[NSUndoManager undoNestedGroup] + 240
 UIKitCore	-[UIUndoGestureInteraction undo:] + 72
 UIKitCore	-[UIKBUndoInteractionHUD performDelegateUndoAndUpdateHUDIfNeeded] + 96
 UIKitCore	-[UIKBUndoInteractionHUD controlActionUpInside:] + 152
 UIKitCore	-[UIApplication sendAction:to:from:forEvent:] + 96
 xxxxx	-[UIApplication(MemoryLeak) swizzled_sendAction:to:from:forEvent:] + 288
 UIKitCore	-[UIControl sendAction:to:from:forEvent:] + 240
 UIKitCore	-[UIControl _sendActionsForEvents:withEvent:] + 408
 UIKitCore	-[UIControl touchesEnded:withEvent:] + 520
 UIKitCore	-[UIWindow _sendTouchesForEvent:] + 2324
 UIKitCore	-[UIWindow sendEvent:] + 3352
 UIKitCore	-[UIApplication sendEvent:] + 336
 UIKitCore	___dispatchPreprocessedEventFromEventQueue + 5880
 UIKitCore	___handleEventQueueInternal + 4924
 UIKitCore	___handleHIDEventFetcherDrain + 108
 CoreFoundation	___CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
 CoreFoundation	___CFRunLoopDoSource0 + 80
 CoreFoundation	___CFRunLoopDoSources0 + 180
 CoreFoundation	___CFRunLoopRun + 1080
 CoreFoundation	CFRunLoopRunSpecific + 464
 GraphicsServices	GSEventRunModal + 104
 UIKitCore	UIApplicationMain + 1936
 xxxxx	main + 148
 libdyld.dylib	_start + 4

问题定位

一时没什么头绪,最老实的办法就是一段一段注释代码。最终,矛头指向了下面这两个方法:

[self addTarget:observer
         action:@selector(textChange:)
forControlEvents:UIControlEventEditingChanged];
- (void)textChange:(UITextField *)textField {
    ... ... 
    UITextRange *selectedRange = [textField markedTextRange];
    if (!selectedRange || !selectedRange.start) {
        if (destText.length > maxLength) {
            textField.text = [destText substringToIndex:maxLength];
        }
    }
}

这段代码原本是用来限制文案长度的。问题在于,三指撤销操作会触发UIControlEventEditingChanged事件,从而执行textChange:。但此时即便输入法还存在高亮的markedTextmarkedTextRange也返回nil。紧接着,代码就会把textField.text给截断了。这一修改,直接破坏了撤销栈的内部状态,再继续执行撤销操作,crash就成了必然。

解决方案

既然问题是同步修改文本导致撤销栈混乱,那把截断操作延迟到下一个runloop执行就行了。方法很简单,将文案判长和截取的逻辑用dispatch_async扔到主队列:

- (void)textChange:(UITextField *)textField {
    dispatch_async(dispatch_get_main_queue(), ^{
        ... ...
    });
}

这样一来,截断操作就不会干扰当前正在进行的撤销流程了。

数字截断后 crash

中文输入的问题解决了,但数字键盘的场景还有坑。当UITextField限制了长度,超过最大长度后继续输入,再进行撤销操作同样会crash。而且上面的异步方案在这个场景下不奏效。目前比较稳妥的做法,是在UITextField的袋里回调中直接拦截输入:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    /// 输入数字后截取字符串仍旧可以触发撤销操作导致crash, 在这里拦截一下
    if (textField.keyboardType == UIKeyboardTypeNumberPad
        && range.location >= textField.tt_maxLength) {
            return NO;
    }
    return YES;
}

简单粗暴,在超出长度后直接拒绝输入,从源头避免crash。

来源:https://www.jb51.net/article/273647.htm
上一篇Flutter RendererBinding作用与源码深度解析 下一篇iOS KeyChain数据持久化操作详解
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
AWS RDS 数据库配置入门与基础操作指南
编程语言 · 2026-07-10

AWS RDS 数据库配置入门与基础操作指南

本文介绍了AWSRDS的基本概念与核心价值,即提供托管式关系数据库服务,简化运维。详细阐述了创建RDS实例的关键配置步骤,包括引擎选择、实例规格、存储与网络设置。最后,指导读者如何通过多种方式安全连接至数据库实例,并开始进行数据操作,为后续应用开发奠定基础。

PHP MVC中AJAX请求无法调用控制器方法的原因与解决方案
编程语言 · 2026-07-10

PHP MVC中AJAX请求无法调用控制器方法的原因与解决方案

PHPMVC中AJAX请求返回整页HTML的常见原因是控制器方法未正确输出响应或未终止执行,导致框架渲染视图。解决方法是在控制器中设置JSON响应头、输出数据后调用exit()明确终止,同时前端使用小写url和dataType: "json "。

Go语言手动构造rsa.PublicKey:正确初始化大整数模数N完整指南
编程语言 · 2026-07-10

Go语言手动构造rsa.PublicKey:正确初始化大整数模数N完整指南

手动构造RSA公钥时,模数N为*big Int类型,不能直接使用超长十进制字面量,需通过SetString或UnmarshalText方法解析字符串。公钥指数E可直接赋值,推荐65537。生产环境应使用rsa GenerateKey生成密钥对,避免手动构造引发的安全和格式错误。

Go语言实现HTTP定时轮询监控多URL响应时间与状态检测
编程语言 · 2026-07-10

Go语言实现HTTP定时轮询监控多URL响应时间与状态检测

使用Go语言实现HTTP定时轮询监控,通过按行分割与Tab解析URL列表,避免闭包陷阱和nil指针,每个URL启动独立ticker安全并发请求,并配置超时控制与资源关闭,确保响应时间与状态码准确检测。

Tkinter中Label标签在主循环动态更新的正确方法
编程语言 · 2026-07-10

Tkinter中Label标签在主循环动态更新的正确方法

在Tkinter中正确动态更新标签的方法:将标签组件的textvariable参数绑定到一个StringVar变量,然后通过调用该变量的 set()方法更新其值,界面会自动刷新。这样避免直接修改text属性或调用update()。此做法实现数据与界面的解耦,代码更简洁,响应更及时,避免手动同步的闪烁,推荐做法。