iOS实现类安卓文本框下方错误提示 点击返回/保存按钮App崩溃求助
崩溃原因定位
你遇到的NSISEngine: Not possible to remove variable类崩溃,本质是AutoLayout引擎检测到约束关联的视图锚点已经失效,但约束没有被正确销毁,属于自动布局层面的生命周期管理问题,和你写的UI弹出逻辑直接相关,核心问题点有3个:
- 错误提示容器的添加层级和约束锚点不匹配:代码把错误提示容器直接添加到
keyWindow上,但约束是绑定当前输入框的父视图(也就是你页面控制器的根view),当你点击返回按钮dismiss页面时,控制器的根view会被释放,但挂在window上的错误提示和关联的约束还留在内存里,引擎查找约束绑定的锚点时找不到对应的视图,就会触发崩溃。同时iOS 13及以上多窗口场景下,直接获取keyWindow本身就不安全,会拿到错误的window实例,进一步加剧视图层级不匹配的问题。 - 错误移除逻辑不完整:清空错误状态时,代码只把错误提示视图设为隐藏、从MapTable中移除条目,但没有把错误提示从父视图上真正摘除,也没有把关联的约束全部标记为失效,导致游离的约束一直持有已经销毁的视图引用。
- 弱引用MapTable没有同步释放关联的视图:代码用
NSMapTable的弱键模式存储输入框和错误视图的映射,当输入框被释放时MapTable会自动清除条目,但对应的错误提示视图本身没有被一起释放,会变成挂在window上的游离视图,残留的约束就会在AutoLayout引擎下次刷新时触发校验失败。
具体修复方案
1. 对齐视图层级和约束锚点
不要把错误提示容器强行加到keyWindow上,要把容器添加到和输入框同一个父视图上,保证容器的生命周期和当前页面完全一致:
- 移除原来的
UIApplication.shared.keyWindow!.addSubview(container)代码 - 改为从输入框的
superview取宿主视图,把容器加到这个宿主视图上,约束锚点全部绑定这个宿主视图,不要跨层级绑定
2. 补全错误提示的销毁逻辑
原来的错误清除逻辑只做了隐藏操作,需要补充完整的销毁流程:
- 拿到对应输入框的错误提示视图后,先把视图上所有关联的约束全部deactivate
- 调用
removeFromSuperview()把错误提示从父视图上摘除 - 再从MapTable中清除映射条目
- 不要只设置
isHidden = true就结束流程
3. 加页面销毁兜底逻辑
在当前表单控制器的viewWillDisappear方法里,遍历页面上所有输入框,主动调用setError(nil)清理所有残留的错误提示,避免页面dismiss后留下游离视图。
关键修改代码示例
修改后的setError方法核心片段
extension UITextField { func setError(_ string: String? = nil, show: Bool = true) { if let rightView = rightView, rightView.tag != 999 { rightViews.setObject(rightView, forKey: self) } // 清除错误状态分支 guard string != nil else { // 恢复原来的右侧视图 if let rightView = rightViews.object(forKey: self) { self.rightView = rightView rightViews.removeObject(forKey: self) } else { self.rightView = nil } // 完整销毁错误提示 if let errorView = errorViews.object(forKey: self) { // 先拆所有关联约束 NSLayoutConstraint.deactivate(errorView.constraints) // 从父视图摘除 errorView.removeFromSuperview() errorViews.removeObject(forKey: self) } return } // 原有创建容器、三角形、提示文字的逻辑保持不变 let container = UIView() container.translatesAutoresizingMaskIntoConstraints = false // ... 中间创建三角、红线、提示label的代码和原来一致 // 修正视图添加和约束绑定逻辑 guard let hostView = self.superview else { return } hostView.addSubview(container) // 激活容器的约束,全部绑定宿主视图 NSLayoutConstraint.activate([ container.widthAnchor.constraint(lessThanOrEqualTo: hostView.widthAnchor, multiplier: 1), container.trailingAnchor.constraint(equalTo: hostView.trailingAnchor, constant: 0), container.topAnchor.constraint(equalTo: hostView.bottomAnchor, constant: 0) ]) // ... 原有隐藏其他提示、设置右侧错误按钮、存储映射的逻辑保持不变 } }
控制器端的兜底清理代码
class FormEditProfileInfoController: UIViewController, UITextFieldDelegate { @IBOutlet weak var txtPOBox: UITextField! override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 页面离开时清理所有残留的错误提示 view.subviews.compactMap { $0 as? UITextField }.forEach { field in field.setError(nil) } } // ... 原有其余代码保持不变 }
内容的提问来源于stack exchange,提问作者janki thakar
相关产品推荐
相关产品推荐

