如何为自定义UIView的UITextView子视图设置代理?
处理自定义View中子UITextView的文本修改事件
我太懂你这个场景了——自己封装了个带俩UITextView的自定义View(也就是你说的父视图A),现在要在ViewController里处理其中子视图C的文本修改逻辑对吧?这种情况核心要注意解耦,绝对不能让VC直接去扒自定义View里的子控件,不然以后你改自定义View的内部结构,VC的代码就得跟着大改,太坑了。下面给你几种iOS开发里常用的靠谱方案:
方案1:自定义View暴露代理接口(最符合iOS设计规范)
这跟系统控件的逻辑完全一致,扩展性最强,适合需要暴露多个事件的场景。
- 第一步:给自定义View A定义专属的代理协议,把你需要的事件暴露出去:
protocol CustomViewADelegate: AnyObject { func customView(_ customView: CustomViewA, textViewCDidChange text: String) // 要是以后需要监听B的事件,直接加对应的方法就行 }
- 第二步:在CustomViewA里添加代理属性,同时让自己成为C的
UITextViewDelegate:
class CustomViewA: UIView { weak var delegate: CustomViewADelegate? private let textViewB = UITextView() private let textViewC = UITextView() override init(frame: CGRect) { super.init(frame: frame) setupTextViews() } required init?(coder: NSCoder) { super.init(coder: coder) setupTextViews() } private func setupTextViews() { // 这里省略B和C的布局、样式配置代码 textViewC.delegate = self } } extension CustomViewA: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { // 确认是子视图C的话,把事件抛给代理 guard textView === textViewC else { return } delegate?.customView(self, textViewCDidChange: textView.text ?? "") } }
- 第三步:在VC里遵守代理协议,实现对应的方法:
class ViewController: UIViewController, CustomViewADelegate { private let customViewA = CustomViewA() override func viewDidLoad() { super.viewDidLoad() customViewA.delegate = self view.addSubview(customViewA) // 这里添加customViewA的布局代码 } func customView(_ customView: CustomViewA, textViewCDidChange text: String) { // 在这里处理C文本变化的逻辑,比如输入校验、更新数据模型等 print("TextView C的文本更新啦:\(text)") } }
方案2:闭包回调(轻量简洁,适合少事件场景)
如果只需要监听一两个事件,闭包会比代理更清爽,代码也更紧凑。
- 在CustomViewA里添加一个闭包属性,用来传递C的文本变化事件:
class CustomViewA: UIView { // 用可选类型,避免强制要求VC必须实现 var textViewCChangeHandler: ((String) -> Void)? private let textViewB = UITextView() private let textViewC = UITextView() // 初始化和布局代码跟上面一致... private func setupTextViews() { textViewC.delegate = self } } extension CustomViewA: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { guard textView === textViewC else { return } textViewCChangeHandler?(textView.text ?? "") } }
- 然后在VC里直接设置闭包逻辑:
class ViewController: UIViewController { private let customViewA = CustomViewA() override func viewDidLoad() { super.viewDidLoad() // 注意用[weak self]避免循环引用 customViewA.textViewCChangeHandler = { [weak self] newText in self?.handleTextViewCUpdate(newText) } view.addSubview(customViewA) } private func handleTextViewCUpdate(_ text: String) { print("TextView C的新文本:\(text)") // 这里写你的业务逻辑 } }
方案3:通知中心(不推荐,特殊场景才用)
如果实在不想用代理或闭包,也可以用NotificationCenter,但这种方式耦合性很高,还容易出现通知泄漏,一般只在跨多层级传递事件时才考虑。
- 在CustomViewA的
textViewDidChange里发送通知:
extension CustomViewA: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { guard textView === textViewC else { return } NotificationCenter.default.post( name: NSNotification.Name("TextViewCContentChanged"), object: textView.text ) } }
- 在VC里监听通知,记得在销毁时移除监听:
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver( self, selector: #selector(handleTextViewCChange(_:)), name: NSNotification.Name("TextViewCContentChanged"), object: nil ) } @objc private func handleTextViewCChange(_ notification: Notification) { if let newText = notification.object as? String { print("TextView C更新:\(newText)") } } deinit { NotificationCenter.default.removeObserver(self) } }
最后说句心里话
优先选代理或者闭包,这俩都是低耦合的方案,完全符合面向对象的设计原则。以后你要是改CustomViewA的内部结构,比如把TextViewC换成别的控件,只要对外的接口不变,VC的代码根本不用动,维护起来超省心。
内容的提问来源于stack exchange,提问作者SrJaimito
相关产品推荐
相关产品推荐

